Update continuity vendor

Pulls in copy and fstest changes

Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
Derek McGowan 2019-08-15 11:03:55 -07:00
parent 81386df917
commit 5a0ff41c81
No known key found for this signature in database
GPG Key ID: F58C5D0A4405ACDB
15 changed files with 228 additions and 78 deletions

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -4,7 +4,7 @@ github.com/containerd/cgroups c4b9ac5c7601384c965b9646fc515884e091ebb9
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
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

View File

@ -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.

View File

@ -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.

View File

@ -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")
} }
} }

View File

@ -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
} }
} }

View File

@ -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
} }
} }

View File

@ -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
} }

View File

@ -49,15 +49,7 @@ 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

View File

@ -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,

View File

@ -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 {

View File

@ -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

View File

@ -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()
} }