Merge pull request #3528 from dmcgowan/overlay-direct-unpack
Add direct unpack support for overlay
This commit is contained in:
commit
0ab7f03fee
255
archive/tar.go
255
archive/tar.go
@ -19,9 +19,7 @@ package archive
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@ -91,11 +89,6 @@ const (
|
||||
// archives.
|
||||
whiteoutMetaPrefix = whiteoutPrefix + whiteoutPrefix
|
||||
|
||||
// whiteoutLinkDir is a directory AUFS uses for storing hardlink links to other
|
||||
// layers. Normally these should not go into exported archives and all changed
|
||||
// hardlinks should be copied to the top layer.
|
||||
whiteoutLinkDir = whiteoutMetaPrefix + "plnk"
|
||||
|
||||
// whiteoutOpaqueDir file means directory has been made opaque - meaning
|
||||
// readdir calls to this directory do not follow to lower layers.
|
||||
whiteoutOpaqueDir = whiteoutMetaPrefix + ".opq"
|
||||
@ -117,11 +110,15 @@ func Apply(ctx context.Context, root string, r io.Reader, opts ...ApplyOpt) (int
|
||||
if options.Filter == nil {
|
||||
options.Filter = all
|
||||
}
|
||||
if options.applyFunc == nil {
|
||||
options.applyFunc = applyNaive
|
||||
}
|
||||
|
||||
return apply(ctx, root, tar.NewReader(r), options)
|
||||
return options.applyFunc(ctx, root, tar.NewReader(r), options)
|
||||
}
|
||||
|
||||
// applyNaive applies a tar stream of an OCI style diff tar.
|
||||
// applyNaive applies a tar stream of an OCI style diff tar to a directory
|
||||
// applying each file as either a whole file or whiteout.
|
||||
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
||||
func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
|
||||
var (
|
||||
@ -131,11 +128,49 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
|
||||
// may occur out of order
|
||||
unpackedPaths = make(map[string]struct{})
|
||||
|
||||
// Used for aufs plink directory
|
||||
aufsTempdir = ""
|
||||
aufsHardlinks = make(map[string]*tar.Header)
|
||||
convertWhiteout = options.ConvertWhiteout
|
||||
)
|
||||
|
||||
if convertWhiteout == nil {
|
||||
// handle whiteouts by removing the target files
|
||||
convertWhiteout = func(hdr *tar.Header, path string) (bool, error) {
|
||||
base := filepath.Base(path)
|
||||
dir := filepath.Dir(path)
|
||||
if base == whiteoutOpaqueDir {
|
||||
_, err := os.Lstat(dir)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = nil // parent was deleted
|
||||
}
|
||||
return err
|
||||
}
|
||||
if path == dir {
|
||||
return nil
|
||||
}
|
||||
if _, exists := unpackedPaths[path]; !exists {
|
||||
err := os.RemoveAll(path)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return false, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(base, whiteoutPrefix) {
|
||||
originalBase := base[len(whiteoutPrefix):]
|
||||
originalPath := filepath.Join(dir, originalBase)
|
||||
|
||||
return false, os.RemoveAll(originalPath)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through the files in the archive.
|
||||
for {
|
||||
select {
|
||||
@ -193,85 +228,21 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
|
||||
if base == "" {
|
||||
parentPath = filepath.Dir(path)
|
||||
}
|
||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
||||
err = mkdirAll(parentPath, 0755)
|
||||
if err := mkparent(ctx, parentPath, root, options.Parents); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// Naive whiteout convert function which handles whiteout files by
|
||||
// removing the target files.
|
||||
if err := validateWhiteout(path); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writeFile, err := convertWhiteout(hdr, path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip AUFS metadata dirs
|
||||
if strings.HasPrefix(hdr.Name, whiteoutMetaPrefix) {
|
||||
// Regular files inside /.wh..wh.plnk can be used as hardlink targets
|
||||
// We don't want this directory, but we need the files in them so that
|
||||
// such hardlinks can be resolved.
|
||||
if strings.HasPrefix(hdr.Name, whiteoutLinkDir) && hdr.Typeflag == tar.TypeReg {
|
||||
basename := filepath.Base(hdr.Name)
|
||||
aufsHardlinks[basename] = hdr
|
||||
if aufsTempdir == "" {
|
||||
if aufsTempdir, err = ioutil.TempDir(os.Getenv("XDG_RUNTIME_DIR"), "dockerplnk"); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer os.RemoveAll(aufsTempdir)
|
||||
}
|
||||
p, err := fs.RootPath(aufsTempdir, basename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := createTarFile(ctx, p, root, hdr, tr); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if hdr.Name != whiteoutOpaqueDir {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(base, whiteoutPrefix) {
|
||||
dir := filepath.Dir(path)
|
||||
if base == whiteoutOpaqueDir {
|
||||
_, err := os.Lstat(dir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = nil // parent was deleted
|
||||
}
|
||||
return err
|
||||
}
|
||||
if path == dir {
|
||||
return nil
|
||||
}
|
||||
if _, exists := unpackedPaths[path]; !exists {
|
||||
err := os.RemoveAll(path)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
originalBase := base[len(whiteoutPrefix):]
|
||||
originalPath := filepath.Join(dir, originalBase)
|
||||
|
||||
// Ensure originalPath is under dir
|
||||
if dir[len(dir)-1] != filepath.Separator {
|
||||
dir += string(filepath.Separator)
|
||||
}
|
||||
if !strings.HasPrefix(originalPath, dir) {
|
||||
return 0, errors.Wrapf(errInvalidArchive, "invalid whiteout name: %v", base)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(originalPath); err != nil {
|
||||
return 0, err
|
||||
return 0, errors.Wrapf(err, "failed to convert whiteout file %q", hdr.Name)
|
||||
}
|
||||
if !writeFile {
|
||||
continue
|
||||
}
|
||||
// If path exits we almost always just want to remove and replace it.
|
||||
@ -289,26 +260,6 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
|
||||
srcData := io.Reader(tr)
|
||||
srcHdr := hdr
|
||||
|
||||
// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
|
||||
// we manually retarget these into the temporary files we extracted them into
|
||||
if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), whiteoutLinkDir) {
|
||||
linkBasename := filepath.Base(hdr.Linkname)
|
||||
srcHdr = aufsHardlinks[linkBasename]
|
||||
if srcHdr == nil {
|
||||
return 0, fmt.Errorf("invalid aufs hardlink")
|
||||
}
|
||||
p, err := fs.RootPath(aufsTempdir, linkBasename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
tmpFile, err := os.Open(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
srcData = tmpFile
|
||||
}
|
||||
|
||||
if err := createTarFile(ctx, path, root, srcHdr, srcData); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -428,6 +379,66 @@ func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header
|
||||
return chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime))
|
||||
}
|
||||
|
||||
func mkparent(ctx context.Context, path, root string, parents []string) error {
|
||||
if dir, err := os.Lstat(path); err == nil {
|
||||
if dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return &os.PathError{
|
||||
Op: "mkparent",
|
||||
Path: path,
|
||||
Err: syscall.ENOTDIR,
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
i := len(path)
|
||||
for i > len(root) && !os.IsPathSeparator(path[i-1]) {
|
||||
i--
|
||||
}
|
||||
|
||||
if i > len(root)+1 {
|
||||
if err := mkparent(ctx, path[:i-1], root, parents); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := mkdir(path, 0755); err != nil {
|
||||
// Check that still doesn't exist
|
||||
dir, err1 := os.Lstat(path)
|
||||
if err1 == nil && dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range parents {
|
||||
ppath, err := fs.RootPath(p, path[len(root):])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir, err := os.Lstat(ppath)
|
||||
if err == nil {
|
||||
if !dir.IsDir() {
|
||||
// Replaced, do not copy attributes
|
||||
break
|
||||
}
|
||||
if err := copyDirInfo(dir, path); err != nil {
|
||||
return err
|
||||
}
|
||||
return copyUpXAttrs(path, ppath)
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.G(ctx).Debugf("parent directory %q not found: default permissions(0755) used", path)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type changeWriter struct {
|
||||
tw *tar.Writer
|
||||
source string
|
||||
@ -598,6 +609,9 @@ func (cw *changeWriter) Close() error {
|
||||
}
|
||||
|
||||
func (cw *changeWriter) includeParents(hdr *tar.Header) error {
|
||||
if cw.addedDirs == nil {
|
||||
return nil
|
||||
}
|
||||
name := strings.TrimRight(hdr.Name, "/")
|
||||
fname := filepath.Join(cw.source, name)
|
||||
parent := filepath.Dir(name)
|
||||
@ -684,3 +698,26 @@ func hardlinkRootPath(root, linkname string) (string, error) {
|
||||
}
|
||||
return targetPath, nil
|
||||
}
|
||||
|
||||
func validateWhiteout(path string) error {
|
||||
base := filepath.Base(path)
|
||||
dir := filepath.Dir(path)
|
||||
|
||||
if base == whiteoutOpaqueDir {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(base, whiteoutPrefix) {
|
||||
originalBase := base[len(whiteoutPrefix):]
|
||||
originalPath := filepath.Join(dir, originalBase)
|
||||
|
||||
// Ensure originalPath is under dir
|
||||
if dir[len(dir)-1] != filepath.Separator {
|
||||
dir += string(filepath.Separator)
|
||||
}
|
||||
if !strings.HasPrefix(originalPath, dir) {
|
||||
return errors.Wrapf(errInvalidArchive, "invalid whiteout name: %v", base)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
183
archive/tar_linux_test.go
Normal file
183
archive/tar_linux_test.go
Normal file
@ -0,0 +1,183 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/containerd/log/logtest"
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/containerd/containerd/pkg/testutil"
|
||||
"github.com/containerd/containerd/snapshots/overlay"
|
||||
"github.com/containerd/continuity/fs"
|
||||
"github.com/containerd/continuity/fs/fstest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestOverlayApply(t *testing.T) {
|
||||
testutil.RequiresRoot(t)
|
||||
|
||||
base, err := ioutil.TempDir("", "test-ovl-diff-apply-")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %+v", err)
|
||||
}
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
if err := overlay.Supported(base); err != nil {
|
||||
t.Skipf("skipping because overlay is not supported %v", err)
|
||||
}
|
||||
fstest.FSSuite(t, overlayDiffApplier{
|
||||
tmp: base,
|
||||
diff: WriteDiff,
|
||||
t: t,
|
||||
})
|
||||
}
|
||||
|
||||
func TestOverlayApplyNoParents(t *testing.T) {
|
||||
testutil.RequiresRoot(t)
|
||||
|
||||
base, err := ioutil.TempDir("", "test-ovl-diff-apply-")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %+v", err)
|
||||
}
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
if err := overlay.Supported(base); err != nil {
|
||||
t.Skipf("skipping because overlay is not supported %v", err)
|
||||
}
|
||||
fstest.FSSuite(t, overlayDiffApplier{
|
||||
tmp: base,
|
||||
diff: func(ctx context.Context, w io.Writer, a, b string) error {
|
||||
cw := newChangeWriter(w, b)
|
||||
cw.addedDirs = nil
|
||||
err := fs.Changes(ctx, a, b, cw.HandleChange)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create diff tar stream")
|
||||
}
|
||||
return cw.Close()
|
||||
},
|
||||
t: t,
|
||||
})
|
||||
}
|
||||
|
||||
type overlayDiffApplier struct {
|
||||
tmp string
|
||||
diff func(context.Context, io.Writer, string, string) error
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
type overlayContext struct {
|
||||
merged string
|
||||
lowers []string
|
||||
mounted bool
|
||||
}
|
||||
|
||||
type contextKey struct{}
|
||||
|
||||
func (d overlayDiffApplier) TestContext(ctx context.Context) (context.Context, func(), error) {
|
||||
merged, err := ioutil.TempDir(d.tmp, "merged")
|
||||
if err != nil {
|
||||
return ctx, nil, errors.Wrap(err, "failed to make merged dir")
|
||||
}
|
||||
|
||||
oc := &overlayContext{
|
||||
merged: merged,
|
||||
}
|
||||
|
||||
ctx = logtest.WithT(ctx, d.t)
|
||||
|
||||
return context.WithValue(ctx, contextKey{}, oc), func() {
|
||||
if oc.mounted {
|
||||
mount.Unmount(oc.merged, 0)
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d overlayDiffApplier) Apply(ctx context.Context, a fstest.Applier) (string, func(), error) {
|
||||
oc := ctx.Value(contextKey{}).(*overlayContext)
|
||||
|
||||
applyCopy, err := ioutil.TempDir(d.tmp, "apply-copy-")
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "failed to create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(applyCopy)
|
||||
|
||||
base := oc.merged
|
||||
if len(oc.lowers) == 1 {
|
||||
base = oc.lowers[0]
|
||||
}
|
||||
|
||||
if err = fs.CopyDir(applyCopy, base); err != nil {
|
||||
return "", nil, errors.Wrap(err, "failed to copy base")
|
||||
}
|
||||
|
||||
if err := a.Apply(applyCopy); err != nil {
|
||||
return "", nil, errors.Wrap(err, "failed to apply changes to copy of base")
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
if err := d.diff(ctx, buf, base, applyCopy); err != nil {
|
||||
return "", nil, errors.Wrap(err, "failed to create diff")
|
||||
}
|
||||
|
||||
if oc.mounted {
|
||||
if err := mount.Unmount(oc.merged, 0); err != nil {
|
||||
return "", nil, errors.Wrap(err, "failed to unmount")
|
||||
}
|
||||
oc.mounted = false
|
||||
}
|
||||
|
||||
next, err := ioutil.TempDir(d.tmp, "lower-")
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "failed to create temp dir")
|
||||
}
|
||||
|
||||
if _, err = Apply(ctx, next, buf, WithConvertWhiteout(OverlayConvertWhiteout), WithParents(oc.lowers)); err != nil {
|
||||
return "", nil, errors.Wrap(err, "failed to apply tar stream")
|
||||
}
|
||||
|
||||
oc.lowers = append([]string{next}, oc.lowers...)
|
||||
|
||||
if len(oc.lowers) == 1 {
|
||||
return oc.lowers[0], nil, nil
|
||||
}
|
||||
|
||||
m := mount.Mount{
|
||||
Type: "overlay",
|
||||
Source: "overlay",
|
||||
Options: []string{
|
||||
fmt.Sprintf("lowerdir=%s", strings.Join(oc.lowers, ":")),
|
||||
},
|
||||
}
|
||||
|
||||
if err := m.Mount(oc.merged); err != nil {
|
||||
return "", nil, errors.Wrapf(err, "failed to mount: %v", m)
|
||||
}
|
||||
oc.mounted = true
|
||||
|
||||
return oc.merged, nil, nil
|
||||
}
|
@ -16,7 +16,19 @@
|
||||
|
||||
package archive
|
||||
|
||||
import "archive/tar"
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
)
|
||||
|
||||
// ApplyOptions provides additional options for an Apply operation
|
||||
type ApplyOptions struct {
|
||||
Filter Filter // Filter tar headers
|
||||
ConvertWhiteout ConvertWhiteout // Convert whiteout files
|
||||
Parents []string // Parent directories to handle inherited attributes without CoW
|
||||
|
||||
applyFunc func(context.Context, string, *tar.Reader, ApplyOptions) (int64, error)
|
||||
}
|
||||
|
||||
// ApplyOpt allows setting mutable archive apply properties on creation
|
||||
type ApplyOpt func(options *ApplyOptions) error
|
||||
@ -24,6 +36,9 @@ type ApplyOpt func(options *ApplyOptions) error
|
||||
// Filter specific files from the archive
|
||||
type Filter func(*tar.Header) (bool, error)
|
||||
|
||||
// ConvertWhiteout converts whiteout files from the archive
|
||||
type ConvertWhiteout func(*tar.Header, string) (bool, error)
|
||||
|
||||
// all allows all files
|
||||
func all(_ *tar.Header) (bool, error) {
|
||||
return true, nil
|
||||
@ -36,3 +51,24 @@ func WithFilter(f Filter) ApplyOpt {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithConvertWhiteout uses the convert function to convert the whiteout files.
|
||||
func WithConvertWhiteout(c ConvertWhiteout) ApplyOpt {
|
||||
return func(options *ApplyOptions) error {
|
||||
options.ConvertWhiteout = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithParents provides parent directories for resolving inherited attributes
|
||||
// directory from the filesystem.
|
||||
// Inherited attributes are searched from first to last, making the first
|
||||
// element in the list the most immediate parent directory.
|
||||
// NOTE: When applying to a filesystem which supports CoW, file attributes
|
||||
// should be inherited by the filesystem.
|
||||
func WithParents(p []string) ApplyOpt {
|
||||
return func(options *ApplyOptions) error {
|
||||
options.Parents = p
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
59
archive/tar_opts_linux.go
Normal file
59
archive/tar_opts_linux.go
Normal file
@ -0,0 +1,59 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// AufsConvertWhiteout converts whiteout files for aufs.
|
||||
func AufsConvertWhiteout(_ *tar.Header, _ string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// OverlayConvertWhiteout converts whiteout files for overlay.
|
||||
func OverlayConvertWhiteout(hdr *tar.Header, path string) (bool, error) {
|
||||
base := filepath.Base(path)
|
||||
dir := filepath.Dir(path)
|
||||
|
||||
// if a directory is marked as opaque, we need to translate that to overlay
|
||||
if base == whiteoutOpaqueDir {
|
||||
// don't write the file itself
|
||||
return false, unix.Setxattr(dir, "trusted.overlay.opaque", []byte{'y'}, 0)
|
||||
}
|
||||
|
||||
// if a file was deleted and we are using overlay, we need to create a character device
|
||||
if strings.HasPrefix(base, whiteoutPrefix) {
|
||||
originalBase := base[len(whiteoutPrefix):]
|
||||
originalPath := filepath.Join(dir, originalBase)
|
||||
|
||||
if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil {
|
||||
return false, err
|
||||
}
|
||||
// don't write the file itself
|
||||
return false, os.Chown(originalPath, hdr.Uid, hdr.Gid)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
@ -18,28 +18,12 @@
|
||||
|
||||
package archive
|
||||
|
||||
// ApplyOptions provides additional options for an Apply operation
|
||||
type ApplyOptions struct {
|
||||
ParentLayerPaths []string // Parent layer paths used for Windows layer apply
|
||||
IsWindowsContainerLayer bool // True if the tar stream to be applied is a Windows Container Layer
|
||||
Filter Filter // Filter tar headers
|
||||
}
|
||||
|
||||
// WithParentLayers adds parent layers to the apply process this is required
|
||||
// for all Windows layers except the base layer.
|
||||
func WithParentLayers(parentPaths []string) ApplyOpt {
|
||||
return func(options *ApplyOptions) error {
|
||||
options.ParentLayerPaths = parentPaths
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AsWindowsContainerLayer indicates that the tar stream to apply is that of
|
||||
// a Windows Container Layer. The caller must be holding SeBackupPrivilege and
|
||||
// SeRestorePrivilege.
|
||||
func AsWindowsContainerLayer() ApplyOpt {
|
||||
return func(options *ApplyOptions) error {
|
||||
options.IsWindowsContainerLayer = true
|
||||
options.applyFunc = applyWindowsLayer
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -20,11 +20,12 @@ package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/continuity/fs"
|
||||
"github.com/containerd/continuity/sysx"
|
||||
"github.com/opencontainers/runc/libcontainer/system"
|
||||
"github.com/pkg/errors"
|
||||
@ -74,10 +75,6 @@ func openFile(name string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
return f, err
|
||||
}
|
||||
|
||||
func mkdirAll(path string, perm os.FileMode) error {
|
||||
return os.MkdirAll(path, perm)
|
||||
}
|
||||
|
||||
func mkdir(path string, perm os.FileMode) error {
|
||||
if err := os.Mkdir(path, perm); err != nil {
|
||||
return err
|
||||
@ -149,11 +146,71 @@ func getxattr(path, attr string) ([]byte, error) {
|
||||
}
|
||||
|
||||
func setxattr(path, key, value string) error {
|
||||
return sysx.LSetxattr(path, key, []byte(value), 0)
|
||||
// Do not set trusted attributes
|
||||
if strings.HasPrefix(key, "trusted.") {
|
||||
return errors.Wrap(unix.ENOTSUP, "admin attributes from archive not supported")
|
||||
}
|
||||
return unix.Lsetxattr(path, key, []byte(value), 0)
|
||||
}
|
||||
|
||||
// apply applies a tar stream of an OCI style diff tar.
|
||||
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
||||
func apply(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
|
||||
return applyNaive(ctx, root, tr, options)
|
||||
func copyDirInfo(fi os.FileInfo, path string) error {
|
||||
st := fi.Sys().(*syscall.Stat_t)
|
||||
if err := os.Lchown(path, int(st.Uid), int(st.Gid)); err != nil {
|
||||
if os.IsPermission(err) {
|
||||
// Normally if uid/gid are the same this would be a no-op, but some
|
||||
// filesystems may still return EPERM... for instance NFS does this.
|
||||
// In such a case, this is not an error.
|
||||
if dstStat, err2 := os.Lstat(path); err2 == nil {
|
||||
st2 := dstStat.Sys().(*syscall.Stat_t)
|
||||
if st.Uid == st2.Uid && st.Gid == st2.Gid {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to chown %s", path)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Chmod(path, fi.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to chmod %s", path)
|
||||
}
|
||||
|
||||
timespec := []unix.Timespec{unix.Timespec(fs.StatAtime(st)), unix.Timespec(fs.StatMtime(st))}
|
||||
if err := unix.UtimesNanoAt(unix.AT_FDCWD, path, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||
return errors.Wrapf(err, "failed to utime %s", path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyUpXAttrs(dst, src string) error {
|
||||
xattrKeys, err := sysx.LListxattr(src)
|
||||
if err != nil {
|
||||
if err == unix.ENOTSUP || err == sysx.ENODATA {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||
}
|
||||
for _, xattr := range xattrKeys {
|
||||
// Do not copy up trusted attributes
|
||||
if strings.HasPrefix(xattr, "trusted.") {
|
||||
continue
|
||||
}
|
||||
data, err := sysx.LGetxattr(src, xattr)
|
||||
if err != nil {
|
||||
if err == unix.ENOTSUP || err == sysx.ENODATA {
|
||||
continue
|
||||
}
|
||||
return errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||
}
|
||||
if err := unix.Lsetxattr(dst, xattr, data, unix.XATTR_CREATE); err != nil {
|
||||
if err == unix.ENOTSUP || err == unix.ENODATA || err == unix.EEXIST {
|
||||
continue
|
||||
}
|
||||
return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@ -36,6 +35,7 @@ import (
|
||||
"github.com/Microsoft/go-winio"
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/containerd/containerd/sys"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -107,10 +107,6 @@ func openFile(name string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
return sys.OpenFileSequential(name, flag, perm)
|
||||
}
|
||||
|
||||
func mkdirAll(path string, perm os.FileMode) error {
|
||||
return sys.MkdirAll(path, perm)
|
||||
}
|
||||
|
||||
func mkdir(path string, perm os.FileMode) error {
|
||||
return os.Mkdir(path, perm)
|
||||
}
|
||||
@ -153,16 +149,8 @@ func setxattr(path, key, value string) error {
|
||||
return errors.New("xattrs not supported on Windows")
|
||||
}
|
||||
|
||||
// apply applies a tar stream of an OCI style diff tar of a Windows layer.
|
||||
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
||||
func apply(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
|
||||
if options.IsWindowsContainerLayer {
|
||||
return applyWindowsLayer(ctx, root, tr, options)
|
||||
}
|
||||
return applyNaive(ctx, root, tr, options)
|
||||
}
|
||||
|
||||
// applyWindowsLayer applies a tar stream of an OCI style diff tar of a Windows layer.
|
||||
// applyWindowsLayer applies a tar stream of an OCI style diff tar of a Windows
|
||||
// layer using the hcsshim layer writer and backup streams.
|
||||
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
||||
func applyWindowsLayer(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
|
||||
home, id := filepath.Split(root)
|
||||
@ -170,7 +158,7 @@ func applyWindowsLayer(ctx context.Context, root string, tr *tar.Reader, options
|
||||
HomeDir: home,
|
||||
}
|
||||
|
||||
w, err := hcsshim.NewLayerWriter(info, id, options.ParentLayerPaths)
|
||||
w, err := hcsshim.NewLayerWriter(info, id, options.Parents)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -443,3 +431,14 @@ func writeBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func copyDirInfo(fi os.FileInfo, path string) error {
|
||||
if err := os.Chmod(path, fi.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to chmod %s", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyUpXAttrs(dst, src string) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -19,10 +19,8 @@ package apply
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/archive"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/diff"
|
||||
"github.com/containerd/containerd/log"
|
||||
@ -94,15 +92,8 @@ func (s *fsApplier) Apply(ctx context.Context, desc ocispec.Descriptor, mounts [
|
||||
rc := &readCounter{
|
||||
r: io.TeeReader(processor, digester.Hash()),
|
||||
}
|
||||
if err := mount.WithTempMount(ctx, mounts, func(root string) error {
|
||||
if _, err := archive.Apply(ctx, root, rc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read any trailing data
|
||||
_, err := io.Copy(ioutil.Discard, rc)
|
||||
return err
|
||||
}); err != nil {
|
||||
if err := apply(ctx, mounts, rc); err != nil {
|
||||
return emptyDesc, err
|
||||
}
|
||||
|
||||
|
128
diff/apply/apply_linux.go
Normal file
128
diff/apply/apply_linux.go
Normal file
@ -0,0 +1,128 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apply
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/archive"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func apply(ctx context.Context, mounts []mount.Mount, r io.Reader) error {
|
||||
switch {
|
||||
case len(mounts) == 1 && mounts[0].Type == "overlay":
|
||||
path, parents, err := getOverlayPath(mounts[0].Options)
|
||||
if err != nil {
|
||||
if errdefs.IsInvalidArgument(err) {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
opts := []archive.ApplyOpt{
|
||||
archive.WithConvertWhiteout(archive.OverlayConvertWhiteout),
|
||||
}
|
||||
if len(parents) > 0 {
|
||||
opts = append(opts, archive.WithParents(parents))
|
||||
}
|
||||
_, err = archive.Apply(ctx, path, r, opts...)
|
||||
return err
|
||||
case len(mounts) == 1 && mounts[0].Type == "aufs":
|
||||
path, parents, err := getAufsPath(mounts[0].Options)
|
||||
if err != nil {
|
||||
if errdefs.IsInvalidArgument(err) {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
opts := []archive.ApplyOpt{
|
||||
archive.WithConvertWhiteout(archive.AufsConvertWhiteout),
|
||||
}
|
||||
if len(parents) > 0 {
|
||||
opts = append(opts, archive.WithParents(parents))
|
||||
}
|
||||
_, err = archive.Apply(ctx, path, r, opts...)
|
||||
return err
|
||||
}
|
||||
return mount.WithTempMount(ctx, mounts, func(root string) error {
|
||||
_, err := archive.Apply(ctx, root, r)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func getOverlayPath(options []string) (upper string, lower []string, err error) {
|
||||
const upperdirPrefix = "upperdir="
|
||||
const lowerdirPrefix = "lowerdir="
|
||||
|
||||
for _, o := range options {
|
||||
if strings.HasPrefix(o, upperdirPrefix) {
|
||||
upper = strings.TrimPrefix(o, upperdirPrefix)
|
||||
} else if strings.HasPrefix(o, lowerdirPrefix) {
|
||||
lower = strings.Split(strings.TrimPrefix(o, lowerdirPrefix), ":")
|
||||
}
|
||||
}
|
||||
if upper == "" {
|
||||
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "upperdir not found")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// getAufsPath handles options as given by the containerd aufs package only,
|
||||
// formatted as "br:<upper>=rw[:<lower>=ro+wh]*"
|
||||
func getAufsPath(options []string) (upper string, lower []string, err error) {
|
||||
const (
|
||||
sep = ":"
|
||||
brPrefix = "br:"
|
||||
rwSuffix = "=rw"
|
||||
roSuffix = "=ro+wh"
|
||||
)
|
||||
for _, o := range options {
|
||||
if strings.HasPrefix(o, brPrefix) {
|
||||
o = strings.TrimPrefix(o, brPrefix)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, b := range strings.Split(o, sep) {
|
||||
if strings.HasSuffix(b, rwSuffix) {
|
||||
if upper != "" {
|
||||
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "multiple rw branch found")
|
||||
}
|
||||
upper = strings.TrimSuffix(b, rwSuffix)
|
||||
} else if strings.HasSuffix(b, roSuffix) {
|
||||
if upper == "" {
|
||||
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "rw branch be first")
|
||||
}
|
||||
lower = append(lower, strings.TrimSuffix(b, roSuffix))
|
||||
} else {
|
||||
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "unhandled aufs suffix")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if upper == "" {
|
||||
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "rw branch not found")
|
||||
}
|
||||
return
|
||||
}
|
81
diff/apply/apply_linux_test.go
Normal file
81
diff/apply/apply_linux_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apply
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetOverlayPath(t *testing.T) {
|
||||
good := []string{"upperdir=/test/upper", "lowerdir=/test/lower1:/test/lower2", "workdir=/test/work"}
|
||||
path, parents, err := getOverlayPath(good)
|
||||
if err != nil {
|
||||
t.Fatalf("Get overlay path failed: %v", err)
|
||||
}
|
||||
if path != "/test/upper" {
|
||||
t.Fatalf("Unexpected upperdir: %q", path)
|
||||
}
|
||||
if len(parents) != 2 || parents[0] != "/test/lower1" || parents[1] != "/test/lower2" {
|
||||
t.Fatalf("Unexpected parents: %v", parents)
|
||||
}
|
||||
|
||||
bad := []string{"lowerdir=/test/lower"}
|
||||
_, _, err = getOverlayPath(bad)
|
||||
if err == nil {
|
||||
t.Fatalf("An error is expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAufsPath(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
options []string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
options: []string{"random:option", "br:/test/rw=rw:/test/ro=ro+wh"},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
options: []string{"random:option"},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
options: []string{"br:/test/ro=ro+wh"},
|
||||
expectErr: true,
|
||||
},
|
||||
} {
|
||||
path, parents, err := getAufsPath(test.options)
|
||||
if test.expectErr {
|
||||
if err == nil {
|
||||
t.Fatalf("An error is expected")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Get aufs path failed: %v", err)
|
||||
}
|
||||
if path != "/test/rw" {
|
||||
t.Fatalf("Unexpected rw dir: %q", path)
|
||||
}
|
||||
if len(parents) != 1 || parents[0] != "/test/ro" {
|
||||
t.Fatalf("Unexpected parents: %v", parents)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// +build !windows
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
@ -16,9 +16,19 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package archive
|
||||
package apply
|
||||
|
||||
// ApplyOptions provides additional options for an Apply operation
|
||||
type ApplyOptions struct {
|
||||
Filter Filter // Filter tar headers
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/containerd/containerd/archive"
|
||||
"github.com/containerd/containerd/mount"
|
||||
)
|
||||
|
||||
func apply(ctx context.Context, mounts []mount.Mount, r io.Reader) error {
|
||||
return mount.WithTempMount(ctx, mounts, func(root string) error {
|
||||
_, err := archive.Apply(ctx, root, r)
|
||||
return err
|
||||
})
|
||||
}
|
@ -139,7 +139,7 @@ func (s windowsDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts
|
||||
return emptyDesc, err
|
||||
}
|
||||
|
||||
if _, err := archive.Apply(ctx, layer, rc, archive.WithParentLayers(parentLayerPaths), archive.AsWindowsContainerLayer()); err != nil {
|
||||
if _, err := archive.Apply(ctx, layer, rc, archive.WithParents(parentLayerPaths), archive.AsWindowsContainerLayer()); err != nil {
|
||||
return emptyDesc, err
|
||||
}
|
||||
|
||||
|
@ -50,25 +50,25 @@ func testLookup(t *testing.T, fsType string) {
|
||||
}
|
||||
defer os.RemoveAll(mnt)
|
||||
|
||||
deviceName, cleanupDevice, err := loopback.New(100 << 20) // 100 MB
|
||||
loop, err := loopback.New(100 << 20) // 100 MB
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if out, err := exec.Command("mkfs", "-t", fsType, deviceName).CombinedOutput(); err != nil {
|
||||
if out, err := exec.Command("mkfs", "-t", fsType, loop.Device).CombinedOutput(); err != nil {
|
||||
// not fatal
|
||||
cleanupDevice()
|
||||
t.Skipf("could not mkfs (%s) %s: %v (out: %q)", fsType, deviceName, err, string(out))
|
||||
loop.Close()
|
||||
t.Skipf("could not mkfs (%s) %s: %v (out: %q)", fsType, loop.Device, err, string(out))
|
||||
}
|
||||
if out, err := exec.Command("mount", deviceName, mnt).CombinedOutput(); err != nil {
|
||||
if out, err := exec.Command("mount", loop.Device, mnt).CombinedOutput(); err != nil {
|
||||
// not fatal
|
||||
cleanupDevice()
|
||||
t.Skipf("could not mount %s: %v (out: %q)", deviceName, err, string(out))
|
||||
loop.Close()
|
||||
t.Skipf("could not mount %s: %v (out: %q)", loop.Device, err, string(out))
|
||||
}
|
||||
defer func() {
|
||||
testutil.Unmount(t, mnt)
|
||||
cleanupDevice()
|
||||
loop.Close()
|
||||
}()
|
||||
assert.Check(t, strings.HasPrefix(deviceName, "/dev/loop"))
|
||||
assert.Check(t, strings.HasPrefix(loop.Device, "/dev/loop"))
|
||||
checkLookup(t, fsType, mnt, mnt)
|
||||
|
||||
newMnt, err := ioutil.TempDir("", "containerd-mountinfo-test-newMnt")
|
||||
|
@ -52,24 +52,24 @@ func boltSnapshotter(t *testing.T) func(context.Context, string) (snapshots.Snap
|
||||
if os.Getpagesize() > 4096 {
|
||||
loopbackSize = int64(650 << 20) // 650 MB
|
||||
}
|
||||
deviceName, cleanupDevice, err := loopback.New(loopbackSize)
|
||||
loop, err := loopback.New(loopbackSize)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if out, err := exec.Command(mkbtrfs, deviceName).CombinedOutput(); err != nil {
|
||||
cleanupDevice()
|
||||
if out, err := exec.Command(mkbtrfs, loop.Device).CombinedOutput(); err != nil {
|
||||
loop.Close()
|
||||
return nil, nil, errors.Wrapf(err, "failed to make btrfs filesystem (out: %q)", out)
|
||||
}
|
||||
if out, err := exec.Command("mount", deviceName, root).CombinedOutput(); err != nil {
|
||||
cleanupDevice()
|
||||
return nil, nil, errors.Wrapf(err, "failed to mount device %s (out: %q)", deviceName, out)
|
||||
if out, err := exec.Command("mount", loop.Device, root).CombinedOutput(); err != nil {
|
||||
loop.Close()
|
||||
return nil, nil, errors.Wrapf(err, "failed to mount device %s (out: %q)", loop.Device, out)
|
||||
}
|
||||
|
||||
snapshotter, err := NewSnapshotter(root)
|
||||
if err != nil {
|
||||
cleanupDevice()
|
||||
loop.Close()
|
||||
return nil, nil, errors.Wrap(err, "failed to create new snapshotter")
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ func boltSnapshotter(t *testing.T) func(context.Context, string) (snapshots.Snap
|
||||
return err
|
||||
}
|
||||
err := mount.UnmountAll(root, unix.MNT_DETACH)
|
||||
if cerr := cleanupDevice(); cerr != nil {
|
||||
if cerr := loop.Close(); cerr != nil {
|
||||
err = errors.Wrap(cerr, "device cleanup failed")
|
||||
}
|
||||
return err
|
||||
|
@ -36,23 +36,23 @@ func testOverlaySupported(t testing.TB, expected bool, mkfs ...string) {
|
||||
}
|
||||
defer os.RemoveAll(mnt)
|
||||
|
||||
deviceName, cleanupDevice, err := loopback.New(100 << 20) // 100 MB
|
||||
loop, err := loopback.New(100 << 20) // 100 MB
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if out, err := exec.Command(mkfs[0], append(mkfs[1:], deviceName)...).CombinedOutput(); err != nil {
|
||||
if out, err := exec.Command(mkfs[0], append(mkfs[1:], loop.Device)...).CombinedOutput(); err != nil {
|
||||
// not fatal
|
||||
cleanupDevice()
|
||||
t.Skipf("could not mkfs (%v) %s: %v (out: %q)", mkfs, deviceName, err, string(out))
|
||||
loop.Close()
|
||||
t.Skipf("could not mkfs (%v) %s: %v (out: %q)", mkfs, loop.Device, err, string(out))
|
||||
}
|
||||
if out, err := exec.Command("mount", deviceName, mnt).CombinedOutput(); err != nil {
|
||||
if out, err := exec.Command("mount", loop.Device, mnt).CombinedOutput(); err != nil {
|
||||
// not fatal
|
||||
cleanupDevice()
|
||||
t.Skipf("could not mount %s: %v (out: %q)", deviceName, err, string(out))
|
||||
loop.Close()
|
||||
t.Skipf("could not mount %s: %v (out: %q)", loop.Device, err, string(out))
|
||||
}
|
||||
defer func() {
|
||||
testutil.Unmount(t, mnt)
|
||||
cleanupDevice()
|
||||
loop.Close()
|
||||
}()
|
||||
workload := func() {
|
||||
err = Supported(mnt)
|
||||
|
@ -4,7 +4,7 @@ github.com/containerd/cgroups c4b9ac5c7601384c965b9646fc515884e091ebb9
|
||||
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
||||
github.com/containerd/fifo bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13
|
||||
github.com/containerd/btrfs af5082808c833de0e79c1e72eea9fea239364877
|
||||
github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4
|
||||
github.com/containerd/continuity f2a389ac0a02ce21c09edd7344677a601970f41c
|
||||
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6
|
||||
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
|
||||
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
||||
|
19
vendor/github.com/containerd/continuity/LICENSE
generated
vendored
19
vendor/github.com/containerd/continuity/LICENSE
generated
vendored
@ -1,6 +1,7 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
@ -175,28 +176,16 @@
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
Copyright The containerd Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
10
vendor/github.com/containerd/continuity/README.md
generated
vendored
10
vendor/github.com/containerd/continuity/README.md
generated
vendored
@ -72,3 +72,13 @@ If you change the proto file you will need to rebuild the generated Go with `go
|
||||
```console
|
||||
$ go generate ./proto
|
||||
```
|
||||
|
||||
## Project details
|
||||
|
||||
continuity is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE).
|
||||
As a containerd sub-project, you will find the:
|
||||
* [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md),
|
||||
* [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS),
|
||||
* and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
|
||||
|
||||
information in our [`containerd/project`](https://github.com/containerd/project) repository.
|
||||
|
51
vendor/github.com/containerd/continuity/fs/copy.go
generated
vendored
51
vendor/github.com/containerd/continuity/fs/copy.go
generated
vendored
@ -32,14 +32,49 @@ var bufferPool = &sync.Pool{
|
||||
},
|
||||
}
|
||||
|
||||
// CopyDir copies the directory from src to dst.
|
||||
// Most efficient copy of files is attempted.
|
||||
func CopyDir(dst, src string) error {
|
||||
inodes := map[uint64]string{}
|
||||
return copyDirectory(dst, src, inodes)
|
||||
// XAttrErrorHandlers transform a non-nil xattr error.
|
||||
// Return nil to ignore an error.
|
||||
// xattrKey can be empty for listxattr operation.
|
||||
type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
|
||||
|
||||
type copyDirOpts struct {
|
||||
xeh XAttrErrorHandler
|
||||
}
|
||||
|
||||
func copyDirectory(dst, src string, inodes map[uint64]string) error {
|
||||
type CopyDirOpt func(*copyDirOpts) error
|
||||
|
||||
// WithXAttrErrorHandler allows specifying XAttrErrorHandler
|
||||
// If nil XAttrErrorHandler is specified (default), CopyDir stops
|
||||
// on a non-nil xattr error.
|
||||
func WithXAttrErrorHandler(xeh XAttrErrorHandler) CopyDirOpt {
|
||||
return func(o *copyDirOpts) error {
|
||||
o.xeh = xeh
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAllowXAttrErrors allows ignoring xattr errors.
|
||||
func WithAllowXAttrErrors() CopyDirOpt {
|
||||
xeh := func(dst, src, xattrKey string, err error) error {
|
||||
return nil
|
||||
}
|
||||
return WithXAttrErrorHandler(xeh)
|
||||
}
|
||||
|
||||
// CopyDir copies the directory from src to dst.
|
||||
// Most efficient copy of files is attempted.
|
||||
func CopyDir(dst, src string, opts ...CopyDirOpt) error {
|
||||
var o copyDirOpts
|
||||
for _, opt := range opts {
|
||||
if err := opt(&o); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
inodes := map[uint64]string{}
|
||||
return copyDirectory(dst, src, inodes, &o)
|
||||
}
|
||||
|
||||
func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) error {
|
||||
stat, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to stat %s", src)
|
||||
@ -75,7 +110,7 @@ func copyDirectory(dst, src string, inodes map[uint64]string) error {
|
||||
|
||||
switch {
|
||||
case fi.IsDir():
|
||||
if err := copyDirectory(target, source, inodes); err != nil {
|
||||
if err := copyDirectory(target, source, inodes, o); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
@ -111,7 +146,7 @@ func copyDirectory(dst, src string, inodes map[uint64]string) error {
|
||||
return errors.Wrap(err, "failed to copy file info")
|
||||
}
|
||||
|
||||
if err := copyXAttrs(target, source); err != nil {
|
||||
if err := copyXAttrs(target, source, o.xeh); err != nil {
|
||||
return errors.Wrap(err, "failed to copy xattrs")
|
||||
}
|
||||
}
|
||||
|
37
vendor/github.com/containerd/continuity/fs/copy_linux.go
generated
vendored
37
vendor/github.com/containerd/continuity/fs/copy_linux.go
generated
vendored
@ -59,6 +59,8 @@ func copyFileInfo(fi os.FileInfo, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
const maxSSizeT = int64(^uint(0) >> 1)
|
||||
|
||||
func copyFileContent(dst, src *os.File) error {
|
||||
st, err := src.Stat()
|
||||
if err != nil {
|
||||
@ -71,7 +73,16 @@ func copyFileContent(dst, src *os.File) error {
|
||||
dstFd := int(dst.Fd())
|
||||
|
||||
for size > 0 {
|
||||
n, err := unix.CopyFileRange(srcFd, nil, dstFd, nil, int(size), 0)
|
||||
// Ensure that we are never trying to copy more than SSIZE_MAX at a
|
||||
// time and at the same time avoids overflows when the file is larger
|
||||
// than 4GB on 32-bit systems.
|
||||
var copySize int
|
||||
if size > maxSSizeT {
|
||||
copySize = int(maxSSizeT)
|
||||
} else {
|
||||
copySize = int(size)
|
||||
}
|
||||
n, err := unix.CopyFileRange(srcFd, nil, dstFd, nil, copySize, 0)
|
||||
if err != nil {
|
||||
if (err != unix.ENOSYS && err != unix.EXDEV) || !first {
|
||||
return errors.Wrap(err, "copy file range failed")
|
||||
@ -90,18 +101,34 @@ func copyFileContent(dst, src *os.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyXAttrs(dst, src string) error {
|
||||
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||
xattrKeys, err := sysx.LListxattr(src)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||
e := errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||
if xeh != nil {
|
||||
e = xeh(dst, src, "", e)
|
||||
}
|
||||
return e
|
||||
}
|
||||
for _, xattr := range xattrKeys {
|
||||
data, err := sysx.LGetxattr(src, xattr)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||
e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||
if xeh != nil {
|
||||
if e = xeh(dst, src, xattr, e); e == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
|
||||
return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||
e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||
if xeh != nil {
|
||||
if e = xeh(dst, src, xattr, e); e == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
|
24
vendor/github.com/containerd/continuity/fs/copy_unix.go
generated
vendored
24
vendor/github.com/containerd/continuity/fs/copy_unix.go
generated
vendored
@ -69,18 +69,34 @@ func copyFileContent(dst, src *os.File) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func copyXAttrs(dst, src string) error {
|
||||
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||
xattrKeys, err := sysx.LListxattr(src)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||
e := errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||
if xeh != nil {
|
||||
e = xeh(dst, src, "", e)
|
||||
}
|
||||
return e
|
||||
}
|
||||
for _, xattr := range xattrKeys {
|
||||
data, err := sysx.LGetxattr(src, xattr)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||
e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||
if xeh != nil {
|
||||
if e = xeh(dst, src, xattr, e); e == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
|
||||
return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||
e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||
if xeh != nil {
|
||||
if e = xeh(dst, src, xattr, e); e == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
|
2
vendor/github.com/containerd/continuity/fs/copy_windows.go
generated
vendored
2
vendor/github.com/containerd/continuity/fs/copy_windows.go
generated
vendored
@ -40,7 +40,7 @@ func copyFileContent(dst, src *os.File) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func copyXAttrs(dst, src string) error {
|
||||
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
8
vendor/github.com/containerd/continuity/fs/fstest/compare.go
generated
vendored
8
vendor/github.com/containerd/continuity/fs/fstest/compare.go
generated
vendored
@ -49,16 +49,8 @@ func CheckDirectoryEqual(d1, d2 string) error {
|
||||
|
||||
diff := diffResourceList(m1.Resources, m2.Resources)
|
||||
if diff.HasDiff() {
|
||||
if len(diff.Deletions) != 0 {
|
||||
return errors.Errorf("directory diff between %s and %s\n%s", d1, d2, diff.String())
|
||||
}
|
||||
// TODO: Also skip Recycle Bin contents in Windows layers which is used to store deleted files in some cases
|
||||
for _, add := range diff.Additions {
|
||||
if ok, _ := metadataFiles[add.Path()]; !ok {
|
||||
return errors.Errorf("directory diff between %s and %s\n%s", d1, d2, diff.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
1
vendor/github.com/containerd/continuity/fs/fstest/compare_windows.go
generated
vendored
1
vendor/github.com/containerd/continuity/fs/fstest/compare_windows.go
generated
vendored
@ -17,6 +17,7 @@
|
||||
package fstest
|
||||
|
||||
// TODO: Any more metadata files generated by Windows layers?
|
||||
// TODO: Also skip Recycle Bin contents in Windows layers which is used to store deleted files in some cases
|
||||
var metadataFiles = map[string]bool{
|
||||
"\\System Volume Information": true,
|
||||
"\\WcSandboxState": true,
|
||||
|
12
vendor/github.com/containerd/continuity/fs/fstest/continuity_util.go
generated
vendored
12
vendor/github.com/containerd/continuity/fs/fstest/continuity_util.go
generated
vendored
@ -42,7 +42,17 @@ type resourceListDifference struct {
|
||||
}
|
||||
|
||||
func (l resourceListDifference) HasDiff() bool {
|
||||
return len(l.Additions) > 0 || len(l.Deletions) > 0 || len(l.Updates) > 0
|
||||
if len(l.Deletions) > 0 || len(l.Updates) > 0 || (len(metadataFiles) == 0 && len(l.Additions) > 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, add := range l.Additions {
|
||||
if ok, _ := metadataFiles[add.Path()]; !ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (l resourceListDifference) String() string {
|
||||
|
33
vendor/github.com/containerd/continuity/fs/path.go
generated
vendored
33
vendor/github.com/containerd/continuity/fs/path.go
generated
vendored
@ -22,7 +22,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -47,9 +46,8 @@ func pathChange(lower, upper *currentPath) (ChangeKind, string) {
|
||||
if upper == nil {
|
||||
return ChangeKindDelete, lower.path
|
||||
}
|
||||
// TODO: compare by directory
|
||||
|
||||
switch i := strings.Compare(lower.path, upper.path); {
|
||||
switch i := directoryCompare(lower.path, upper.path); {
|
||||
case i < 0:
|
||||
// File in lower that is not in upper
|
||||
return ChangeKindDelete, lower.path
|
||||
@ -61,6 +59,35 @@ func pathChange(lower, upper *currentPath) (ChangeKind, string) {
|
||||
}
|
||||
}
|
||||
|
||||
func directoryCompare(a, b string) int {
|
||||
l := len(a)
|
||||
if len(b) < l {
|
||||
l = len(b)
|
||||
}
|
||||
for i := 0; i < l; i++ {
|
||||
c1, c2 := a[i], b[i]
|
||||
if c1 == filepath.Separator {
|
||||
c1 = byte(0)
|
||||
}
|
||||
if c2 == filepath.Separator {
|
||||
c2 = byte(0)
|
||||
}
|
||||
if c1 < c2 {
|
||||
return -1
|
||||
}
|
||||
if c1 > c2 {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
if len(a) < len(b) {
|
||||
return -1
|
||||
}
|
||||
if len(a) > len(b) {
|
||||
return +1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func sameFile(f1, f2 *currentPath) (bool, error) {
|
||||
if os.SameFile(f1.f, f2.f) {
|
||||
return true, nil
|
||||
|
55
vendor/github.com/containerd/continuity/testutil/loopback/loopback_linux.go
generated
vendored
55
vendor/github.com/containerd/continuity/testutil/loopback/loopback_linux.go
generated
vendored
@ -22,24 +22,25 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// New creates a loopback device, and returns its device name (/dev/loopX), and its clean-up function.
|
||||
func New(size int64) (string, func() error, error) {
|
||||
// New creates a loopback device
|
||||
func New(size int64) (*Loopback, error) {
|
||||
// create temporary file for the disk image
|
||||
file, err := ioutil.TempFile("", "containerd-test-loopback")
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "could not create temporary file for loopback")
|
||||
return nil, errors.Wrap(err, "could not create temporary file for loopback")
|
||||
}
|
||||
|
||||
if err := file.Truncate(size); err != nil {
|
||||
file.Close()
|
||||
os.Remove(file.Name())
|
||||
return "", nil, errors.Wrap(err, "failed to resize temp file")
|
||||
return nil, errors.Wrap(err, "failed to resize temp file")
|
||||
}
|
||||
file.Close()
|
||||
|
||||
@ -48,7 +49,7 @@ func New(size int64) (string, func() error, error) {
|
||||
p, err := losetup.Output()
|
||||
if err != nil {
|
||||
os.Remove(file.Name())
|
||||
return "", nil, errors.Wrap(err, "loopback setup failed")
|
||||
return nil, errors.Wrap(err, "loopback setup failed")
|
||||
}
|
||||
|
||||
deviceName := strings.TrimSpace(string(p))
|
||||
@ -68,5 +69,47 @@ func New(size int64) (string, func() error, error) {
|
||||
return os.Remove(file.Name())
|
||||
}
|
||||
|
||||
return deviceName, cleanup, nil
|
||||
l := Loopback{
|
||||
File: file.Name(),
|
||||
Device: deviceName,
|
||||
close: cleanup,
|
||||
}
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
// Loopback device
|
||||
type Loopback struct {
|
||||
// File is the underlying sparse file
|
||||
File string
|
||||
// Device is /dev/loopX
|
||||
Device string
|
||||
close func() error
|
||||
}
|
||||
|
||||
// SoftSize returns st_size
|
||||
func (l *Loopback) SoftSize() (int64, error) {
|
||||
st, err := os.Stat(l.File)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return st.Size(), nil
|
||||
}
|
||||
|
||||
// HardSize returns st_blocks * 512; see stat(2)
|
||||
func (l *Loopback) HardSize() (int64, error) {
|
||||
st, err := os.Stat(l.File)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
st2, ok := st.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return 0, errors.New("st.Sys() is not a *syscall.Stat_t")
|
||||
}
|
||||
// NOTE: st_blocks has nothing to do with st_blksize; see stat(2)
|
||||
return st2.Blocks * 512, nil
|
||||
}
|
||||
|
||||
// Close detaches the device and removes the underlying file
|
||||
func (l *Loopback) Close() error {
|
||||
return l.close()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user