Merge pull request #3528 from dmcgowan/overlay-direct-unpack
Add direct unpack support for overlay
This commit is contained in:
commit
0ab7f03fee
257
archive/tar.go
257
archive/tar.go
@ -19,9 +19,7 @@ package archive
|
|||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -91,11 +89,6 @@ const (
|
|||||||
// archives.
|
// archives.
|
||||||
whiteoutMetaPrefix = whiteoutPrefix + whiteoutPrefix
|
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
|
// whiteoutOpaqueDir file means directory has been made opaque - meaning
|
||||||
// readdir calls to this directory do not follow to lower layers.
|
// readdir calls to this directory do not follow to lower layers.
|
||||||
whiteoutOpaqueDir = whiteoutMetaPrefix + ".opq"
|
whiteoutOpaqueDir = whiteoutMetaPrefix + ".opq"
|
||||||
@ -117,11 +110,15 @@ func Apply(ctx context.Context, root string, r io.Reader, opts ...ApplyOpt) (int
|
|||||||
if options.Filter == nil {
|
if options.Filter == nil {
|
||||||
options.Filter = all
|
options.Filter = all
|
||||||
}
|
}
|
||||||
|
if options.applyFunc == nil {
|
||||||
return apply(ctx, root, tar.NewReader(r), options)
|
options.applyFunc = applyNaive
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyNaive applies a tar stream of an OCI style diff tar.
|
return options.applyFunc(ctx, root, tar.NewReader(r), options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// 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) {
|
func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
|
||||||
var (
|
var (
|
||||||
@ -131,11 +128,49 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
|
|||||||
// may occur out of order
|
// may occur out of order
|
||||||
unpackedPaths = make(map[string]struct{})
|
unpackedPaths = make(map[string]struct{})
|
||||||
|
|
||||||
// Used for aufs plink directory
|
convertWhiteout = options.ConvertWhiteout
|
||||||
aufsTempdir = ""
|
|
||||||
aufsHardlinks = make(map[string]*tar.Header)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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.
|
// Iterate through the files in the archive.
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -193,85 +228,21 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
|
|||||||
if base == "" {
|
if base == "" {
|
||||||
parentPath = filepath.Dir(path)
|
parentPath = filepath.Dir(path)
|
||||||
}
|
}
|
||||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
if err := mkparent(ctx, parentPath, root, options.Parents); err != nil {
|
||||||
err = mkdirAll(parentPath, 0755)
|
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 {
|
if err != nil {
|
||||||
return 0, err
|
return 0, errors.Wrapf(err, "failed to convert whiteout file %q", hdr.Name)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
if !writeFile {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// If path exits we almost always just want to remove and replace it.
|
// 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)
|
srcData := io.Reader(tr)
|
||||||
srcHdr := hdr
|
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 {
|
if err := createTarFile(ctx, path, root, srcHdr, srcData); err != nil {
|
||||||
return 0, err
|
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))
|
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 {
|
type changeWriter struct {
|
||||||
tw *tar.Writer
|
tw *tar.Writer
|
||||||
source string
|
source string
|
||||||
@ -598,6 +609,9 @@ func (cw *changeWriter) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cw *changeWriter) includeParents(hdr *tar.Header) error {
|
func (cw *changeWriter) includeParents(hdr *tar.Header) error {
|
||||||
|
if cw.addedDirs == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
name := strings.TrimRight(hdr.Name, "/")
|
name := strings.TrimRight(hdr.Name, "/")
|
||||||
fname := filepath.Join(cw.source, name)
|
fname := filepath.Join(cw.source, name)
|
||||||
parent := filepath.Dir(name)
|
parent := filepath.Dir(name)
|
||||||
@ -684,3 +698,26 @@ func hardlinkRootPath(root, linkname string) (string, error) {
|
|||||||
}
|
}
|
||||||
return targetPath, nil
|
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
|
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
|
// ApplyOpt allows setting mutable archive apply properties on creation
|
||||||
type ApplyOpt func(options *ApplyOptions) error
|
type ApplyOpt func(options *ApplyOptions) error
|
||||||
@ -24,6 +36,9 @@ type ApplyOpt func(options *ApplyOptions) error
|
|||||||
// Filter specific files from the archive
|
// Filter specific files from the archive
|
||||||
type Filter func(*tar.Header) (bool, error)
|
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
|
// all allows all files
|
||||||
func all(_ *tar.Header) (bool, error) {
|
func all(_ *tar.Header) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
@ -36,3 +51,24 @@ func WithFilter(f Filter) ApplyOpt {
|
|||||||
return nil
|
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
|
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
|
// AsWindowsContainerLayer indicates that the tar stream to apply is that of
|
||||||
// a Windows Container Layer. The caller must be holding SeBackupPrivilege and
|
// a Windows Container Layer. The caller must be holding SeBackupPrivilege and
|
||||||
// SeRestorePrivilege.
|
// SeRestorePrivilege.
|
||||||
func AsWindowsContainerLayer() ApplyOpt {
|
func AsWindowsContainerLayer() ApplyOpt {
|
||||||
return func(options *ApplyOptions) error {
|
return func(options *ApplyOptions) error {
|
||||||
options.IsWindowsContainerLayer = true
|
options.applyFunc = applyWindowsLayer
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,11 +20,12 @@ package archive
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"context"
|
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/fs"
|
||||||
"github.com/containerd/continuity/sysx"
|
"github.com/containerd/continuity/sysx"
|
||||||
"github.com/opencontainers/runc/libcontainer/system"
|
"github.com/opencontainers/runc/libcontainer/system"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -74,10 +75,6 @@ func openFile(name string, flag int, perm os.FileMode) (*os.File, error) {
|
|||||||
return f, err
|
return f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func mkdirAll(path string, perm os.FileMode) error {
|
|
||||||
return os.MkdirAll(path, perm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mkdir(path string, perm os.FileMode) error {
|
func mkdir(path string, perm os.FileMode) error {
|
||||||
if err := os.Mkdir(path, perm); err != nil {
|
if err := os.Mkdir(path, perm); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -149,11 +146,71 @@ func getxattr(path, attr string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setxattr(path, key, value string) 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.
|
func copyDirInfo(fi os.FileInfo, path string) error {
|
||||||
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
st := fi.Sys().(*syscall.Stat_t)
|
||||||
func apply(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
|
if err := os.Lchown(path, int(st.Uid), int(st.Gid)); err != nil {
|
||||||
return applyNaive(ctx, root, tr, options)
|
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"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@ -36,6 +35,7 @@ import (
|
|||||||
"github.com/Microsoft/go-winio"
|
"github.com/Microsoft/go-winio"
|
||||||
"github.com/Microsoft/hcsshim"
|
"github.com/Microsoft/hcsshim"
|
||||||
"github.com/containerd/containerd/sys"
|
"github.com/containerd/containerd/sys"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -107,10 +107,6 @@ func openFile(name string, flag int, perm os.FileMode) (*os.File, error) {
|
|||||||
return sys.OpenFileSequential(name, flag, perm)
|
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 {
|
func mkdir(path string, perm os.FileMode) error {
|
||||||
return os.Mkdir(path, perm)
|
return os.Mkdir(path, perm)
|
||||||
}
|
}
|
||||||
@ -153,16 +149,8 @@ func setxattr(path, key, value string) error {
|
|||||||
return errors.New("xattrs not supported on Windows")
|
return errors.New("xattrs not supported on Windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply 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
|
||||||
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
// layer using the hcsshim layer writer and backup streams.
|
||||||
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.
|
|
||||||
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
// 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) {
|
func applyWindowsLayer(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
|
||||||
home, id := filepath.Split(root)
|
home, id := filepath.Split(root)
|
||||||
@ -170,7 +158,7 @@ func applyWindowsLayer(ctx context.Context, root string, tr *tar.Reader, options
|
|||||||
HomeDir: home,
|
HomeDir: home,
|
||||||
}
|
}
|
||||||
|
|
||||||
w, err := hcsshim.NewLayerWriter(info, id, options.ParentLayerPaths)
|
w, err := hcsshim.NewLayerWriter(info, id, options.Parents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/containerd/archive"
|
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/diff"
|
"github.com/containerd/containerd/diff"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
@ -94,15 +92,8 @@ func (s *fsApplier) Apply(ctx context.Context, desc ocispec.Descriptor, mounts [
|
|||||||
rc := &readCounter{
|
rc := &readCounter{
|
||||||
r: io.TeeReader(processor, digester.Hash()),
|
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
|
if err := apply(ctx, mounts, rc); err != nil {
|
||||||
_, err := io.Copy(ioutil.Discard, rc)
|
|
||||||
return err
|
|
||||||
}); err != nil {
|
|
||||||
return emptyDesc, err
|
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.
|
Copyright The containerd Authors.
|
||||||
@ -16,9 +16,19 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package archive
|
package apply
|
||||||
|
|
||||||
// ApplyOptions provides additional options for an Apply operation
|
import (
|
||||||
type ApplyOptions struct {
|
"context"
|
||||||
Filter Filter // Filter tar headers
|
"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
|
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
|
return emptyDesc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,25 +50,25 @@ func testLookup(t *testing.T, fsType string) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(mnt)
|
defer os.RemoveAll(mnt)
|
||||||
|
|
||||||
deviceName, cleanupDevice, err := loopback.New(100 << 20) // 100 MB
|
loop, err := loopback.New(100 << 20) // 100 MB
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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
|
// not fatal
|
||||||
cleanupDevice()
|
loop.Close()
|
||||||
t.Skipf("could not mkfs (%s) %s: %v (out: %q)", fsType, deviceName, err, string(out))
|
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
|
// not fatal
|
||||||
cleanupDevice()
|
loop.Close()
|
||||||
t.Skipf("could not mount %s: %v (out: %q)", deviceName, err, string(out))
|
t.Skipf("could not mount %s: %v (out: %q)", loop.Device, err, string(out))
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
testutil.Unmount(t, mnt)
|
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)
|
checkLookup(t, fsType, mnt, mnt)
|
||||||
|
|
||||||
newMnt, err := ioutil.TempDir("", "containerd-mountinfo-test-newMnt")
|
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 {
|
if os.Getpagesize() > 4096 {
|
||||||
loopbackSize = int64(650 << 20) // 650 MB
|
loopbackSize = int64(650 << 20) // 650 MB
|
||||||
}
|
}
|
||||||
deviceName, cleanupDevice, err := loopback.New(loopbackSize)
|
loop, err := loopback.New(loopbackSize)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if out, err := exec.Command(mkbtrfs, deviceName).CombinedOutput(); err != nil {
|
if out, err := exec.Command(mkbtrfs, loop.Device).CombinedOutput(); err != nil {
|
||||||
cleanupDevice()
|
loop.Close()
|
||||||
return nil, nil, errors.Wrapf(err, "failed to make btrfs filesystem (out: %q)", out)
|
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 {
|
if out, err := exec.Command("mount", loop.Device, root).CombinedOutput(); err != nil {
|
||||||
cleanupDevice()
|
loop.Close()
|
||||||
return nil, nil, errors.Wrapf(err, "failed to mount device %s (out: %q)", deviceName, out)
|
return nil, nil, errors.Wrapf(err, "failed to mount device %s (out: %q)", loop.Device, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshotter, err := NewSnapshotter(root)
|
snapshotter, err := NewSnapshotter(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanupDevice()
|
loop.Close()
|
||||||
return nil, nil, errors.Wrap(err, "failed to create new snapshotter")
|
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
|
return err
|
||||||
}
|
}
|
||||||
err := mount.UnmountAll(root, unix.MNT_DETACH)
|
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")
|
err = errors.Wrap(cerr, "device cleanup failed")
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
@ -36,23 +36,23 @@ func testOverlaySupported(t testing.TB, expected bool, mkfs ...string) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(mnt)
|
defer os.RemoveAll(mnt)
|
||||||
|
|
||||||
deviceName, cleanupDevice, err := loopback.New(100 << 20) // 100 MB
|
loop, err := loopback.New(100 << 20) // 100 MB
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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
|
// not fatal
|
||||||
cleanupDevice()
|
loop.Close()
|
||||||
t.Skipf("could not mkfs (%v) %s: %v (out: %q)", mkfs, deviceName, err, string(out))
|
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
|
// not fatal
|
||||||
cleanupDevice()
|
loop.Close()
|
||||||
t.Skipf("could not mount %s: %v (out: %q)", deviceName, err, string(out))
|
t.Skipf("could not mount %s: %v (out: %q)", loop.Device, err, string(out))
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
testutil.Unmount(t, mnt)
|
testutil.Unmount(t, mnt)
|
||||||
cleanupDevice()
|
loop.Close()
|
||||||
}()
|
}()
|
||||||
workload := func() {
|
workload := func() {
|
||||||
err = Supported(mnt)
|
err = Supported(mnt)
|
||||||
|
@ -4,7 +4,7 @@ github.com/containerd/cgroups c4b9ac5c7601384c965b9646fc515884e091ebb9
|
|||||||
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
||||||
github.com/containerd/fifo bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13
|
github.com/containerd/fifo bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13
|
||||||
github.com/containerd/btrfs af5082808c833de0e79c1e72eea9fea239364877
|
github.com/containerd/btrfs af5082808c833de0e79c1e72eea9fea239364877
|
||||||
github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4
|
github.com/containerd/continuity f2a389ac0a02ce21c09edd7344677a601970f41c
|
||||||
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6
|
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6
|
||||||
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
|
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
|
||||||
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
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
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
https://www.apache.org/licenses/
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
@ -175,28 +176,16 @@
|
|||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
Copyright The containerd Authors
|
||||||
|
|
||||||
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}
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
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
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
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
|
```console
|
||||||
$ go generate ./proto
|
$ 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.
|
// XAttrErrorHandlers transform a non-nil xattr error.
|
||||||
// Most efficient copy of files is attempted.
|
// Return nil to ignore an error.
|
||||||
func CopyDir(dst, src string) error {
|
// xattrKey can be empty for listxattr operation.
|
||||||
inodes := map[uint64]string{}
|
type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
|
||||||
return copyDirectory(dst, src, inodes)
|
|
||||||
|
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)
|
stat, err := os.Stat(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "failed to stat %s", src)
|
return errors.Wrapf(err, "failed to stat %s", src)
|
||||||
@ -75,7 +110,7 @@ func copyDirectory(dst, src string, inodes map[uint64]string) error {
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case fi.IsDir():
|
case fi.IsDir():
|
||||||
if err := copyDirectory(target, source, inodes); err != nil {
|
if err := copyDirectory(target, source, inodes, o); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@ -111,7 +146,7 @@ func copyDirectory(dst, src string, inodes map[uint64]string) error {
|
|||||||
return errors.Wrap(err, "failed to copy file info")
|
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")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxSSizeT = int64(^uint(0) >> 1)
|
||||||
|
|
||||||
func copyFileContent(dst, src *os.File) error {
|
func copyFileContent(dst, src *os.File) error {
|
||||||
st, err := src.Stat()
|
st, err := src.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -71,7 +73,16 @@ func copyFileContent(dst, src *os.File) error {
|
|||||||
dstFd := int(dst.Fd())
|
dstFd := int(dst.Fd())
|
||||||
|
|
||||||
for size > 0 {
|
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 != nil {
|
||||||
if (err != unix.ENOSYS && err != unix.EXDEV) || !first {
|
if (err != unix.ENOSYS && err != unix.EXDEV) || !first {
|
||||||
return errors.Wrap(err, "copy file range failed")
|
return errors.Wrap(err, "copy file range failed")
|
||||||
@ -90,18 +101,34 @@ func copyFileContent(dst, src *os.File) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyXAttrs(dst, src string) error {
|
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||||
xattrKeys, err := sysx.LListxattr(src)
|
xattrKeys, err := sysx.LListxattr(src)
|
||||||
if err != nil {
|
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 {
|
for _, xattr := range xattrKeys {
|
||||||
data, err := sysx.LGetxattr(src, xattr)
|
data, err := sysx.LGetxattr(src, xattr)
|
||||||
if err != nil {
|
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 {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyXAttrs(dst, src string) error {
|
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||||
xattrKeys, err := sysx.LListxattr(src)
|
xattrKeys, err := sysx.LListxattr(src)
|
||||||
if err != nil {
|
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 {
|
for _, xattr := range xattrKeys {
|
||||||
data, err := sysx.LGetxattr(src, xattr)
|
data, err := sysx.LGetxattr(src, xattr)
|
||||||
if err != nil {
|
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 {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyXAttrs(dst, src string) error {
|
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||||
return nil
|
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)
|
diff := diffResourceList(m1.Resources, m2.Resources)
|
||||||
if diff.HasDiff() {
|
if diff.HasDiff() {
|
||||||
if len(diff.Deletions) != 0 {
|
|
||||||
return errors.Errorf("directory diff between %s and %s\n%s", d1, d2, diff.String())
|
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
|
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
|
package fstest
|
||||||
|
|
||||||
// TODO: Any more metadata files generated by Windows layers?
|
// 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{
|
var metadataFiles = map[string]bool{
|
||||||
"\\System Volume Information": true,
|
"\\System Volume Information": true,
|
||||||
"\\WcSandboxState": 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 {
|
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 {
|
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"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@ -47,9 +46,8 @@ func pathChange(lower, upper *currentPath) (ChangeKind, string) {
|
|||||||
if upper == nil {
|
if upper == nil {
|
||||||
return ChangeKindDelete, lower.path
|
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:
|
case i < 0:
|
||||||
// File in lower that is not in upper
|
// File in lower that is not in upper
|
||||||
return ChangeKindDelete, lower.path
|
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) {
|
func sameFile(f1, f2 *currentPath) (bool, error) {
|
||||||
if os.SameFile(f1.f, f2.f) {
|
if os.SameFile(f1.f, f2.f) {
|
||||||
return true, nil
|
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"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates a loopback device, and returns its device name (/dev/loopX), and its clean-up function.
|
// New creates a loopback device
|
||||||
func New(size int64) (string, func() error, error) {
|
func New(size int64) (*Loopback, error) {
|
||||||
// create temporary file for the disk image
|
// create temporary file for the disk image
|
||||||
file, err := ioutil.TempFile("", "containerd-test-loopback")
|
file, err := ioutil.TempFile("", "containerd-test-loopback")
|
||||||
if err != nil {
|
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 {
|
if err := file.Truncate(size); err != nil {
|
||||||
file.Close()
|
file.Close()
|
||||||
os.Remove(file.Name())
|
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()
|
file.Close()
|
||||||
|
|
||||||
@ -48,7 +49,7 @@ func New(size int64) (string, func() error, error) {
|
|||||||
p, err := losetup.Output()
|
p, err := losetup.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Remove(file.Name())
|
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))
|
deviceName := strings.TrimSpace(string(p))
|
||||||
@ -68,5 +69,47 @@ func New(size int64) (string, func() error, error) {
|
|||||||
return os.Remove(file.Name())
|
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