Add direct unpack support for overlay and aufs
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
parent
f06e605f1a
commit
81386df917
114
archive/tar.go
114
archive/tar.go
@ -19,9 +19,7 @@ package archive
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@ -91,11 +89,6 @@ const (
|
||||
// archives.
|
||||
whiteoutMetaPrefix = whiteoutPrefix + whiteoutPrefix
|
||||
|
||||
// whiteoutLinkDir is a directory AUFS uses for storing hardlink links to other
|
||||
// layers. Normally these should not go into exported archives and all changed
|
||||
// hardlinks should be copied to the top layer.
|
||||
whiteoutLinkDir = whiteoutMetaPrefix + "plnk"
|
||||
|
||||
// whiteoutOpaqueDir file means directory has been made opaque - meaning
|
||||
// readdir calls to this directory do not follow to lower layers.
|
||||
whiteoutOpaqueDir = whiteoutMetaPrefix + ".opq"
|
||||
@ -130,10 +123,6 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
|
||||
// Used for handling opaque directory markers which
|
||||
// may occur out of order
|
||||
unpackedPaths = make(map[string]struct{})
|
||||
|
||||
// Used for aufs plink directory
|
||||
aufsTempdir = ""
|
||||
aufsHardlinks = make(map[string]*tar.Header)
|
||||
)
|
||||
|
||||
// Iterate through the files in the archive.
|
||||
@ -201,40 +190,15 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Naive whiteout convert function which handles whiteout files 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 0, err
|
||||
return false, err
|
||||
}
|
||||
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
@ -252,26 +216,29 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
continue
|
||||
return false, err
|
||||
}
|
||||
|
||||
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 0, errors.Wrapf(errInvalidArchive, "invalid whiteout name: %v", base)
|
||||
return false, os.RemoveAll(originalPath)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(originalPath); err != nil {
|
||||
return true, nil
|
||||
}
|
||||
if options.ConvertWhiteout != nil {
|
||||
convertWhiteout = options.ConvertWhiteout
|
||||
}
|
||||
if err := validateWhiteout(path); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writeFile, err := convertWhiteout(hdr, path)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "failed to convert whiteout file %q", hdr.Name)
|
||||
}
|
||||
if !writeFile {
|
||||
continue
|
||||
}
|
||||
// If path exits we almost always just want to remove and replace it.
|
||||
@ -289,26 +256,6 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
|
||||
srcData := io.Reader(tr)
|
||||
srcHdr := hdr
|
||||
|
||||
// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
|
||||
// we manually retarget these into the temporary files we extracted them into
|
||||
if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), whiteoutLinkDir) {
|
||||
linkBasename := filepath.Base(hdr.Linkname)
|
||||
srcHdr = aufsHardlinks[linkBasename]
|
||||
if srcHdr == nil {
|
||||
return 0, fmt.Errorf("invalid aufs hardlink")
|
||||
}
|
||||
p, err := fs.RootPath(aufsTempdir, linkBasename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
tmpFile, err := os.Open(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
srcData = tmpFile
|
||||
}
|
||||
|
||||
if err := createTarFile(ctx, path, root, srcHdr, srcData); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -684,3 +631,26 @@ func hardlinkRootPath(root, linkname string) (string, error) {
|
||||
}
|
||||
return targetPath, nil
|
||||
}
|
||||
|
||||
func validateWhiteout(path string) error {
|
||||
base := filepath.Base(path)
|
||||
dir := filepath.Dir(path)
|
||||
|
||||
if base == whiteoutOpaqueDir {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(base, whiteoutPrefix) {
|
||||
originalBase := base[len(whiteoutPrefix):]
|
||||
originalPath := filepath.Join(dir, originalBase)
|
||||
|
||||
// Ensure originalPath is under dir
|
||||
if dir[len(dir)-1] != filepath.Separator {
|
||||
dir += string(filepath.Separator)
|
||||
}
|
||||
if !strings.HasPrefix(originalPath, dir) {
|
||||
return errors.Wrapf(errInvalidArchive, "invalid whiteout name: %v", base)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -24,6 +24,9 @@ type ApplyOpt func(options *ApplyOptions) error
|
||||
// Filter specific files from the archive
|
||||
type Filter func(*tar.Header) (bool, error)
|
||||
|
||||
// ConvertWhiteout converts whiteout files from the archive
|
||||
type ConvertWhiteout func(*tar.Header, string) (bool, error)
|
||||
|
||||
// all allows all files
|
||||
func all(_ *tar.Header) (bool, error) {
|
||||
return true, nil
|
||||
@ -36,3 +39,11 @@ func WithFilter(f Filter) ApplyOpt {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithConvertWhiteout uses the convert function to convert the whiteout files.
|
||||
func WithConvertWhiteout(c ConvertWhiteout) ApplyOpt {
|
||||
return func(options *ApplyOptions) error {
|
||||
options.ConvertWhiteout = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
65
archive/tar_opts_linux.go
Normal file
65
archive/tar_opts_linux.go
Normal file
@ -0,0 +1,65 @@
|
||||
// +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"
|
||||
)
|
||||
|
||||
// ApplyOptions provides additional options for an Apply operation
|
||||
type ApplyOptions struct {
|
||||
Filter Filter // Filter tar headers
|
||||
ConvertWhiteout ConvertWhiteout // Convert whiteout files
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// +build !windows
|
||||
// +build !linux,!windows
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
@ -21,4 +21,5 @@ package archive
|
||||
// ApplyOptions provides additional options for an Apply operation
|
||||
type ApplyOptions struct {
|
||||
Filter Filter // Filter tar headers
|
||||
ConvertWhiteout ConvertWhiteout // Convert whiteout files
|
||||
}
|
@ -23,6 +23,7 @@ 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
|
||||
ConvertWhiteout ConvertWhiteout // Convert whiteout files
|
||||
}
|
||||
|
||||
// WithParentLayers adds parent layers to the apply process this is required
|
||||
|
@ -19,10 +19,8 @@ package apply
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/archive"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/diff"
|
||||
"github.com/containerd/containerd/log"
|
||||
@ -94,15 +92,8 @@ func (s *fsApplier) Apply(ctx context.Context, desc ocispec.Descriptor, mounts [
|
||||
rc := &readCounter{
|
||||
r: io.TeeReader(processor, digester.Hash()),
|
||||
}
|
||||
if err := mount.WithTempMount(ctx, mounts, func(root string) error {
|
||||
if _, err := archive.Apply(ctx, root, rc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read any trailing data
|
||||
_, err := io.Copy(ioutil.Discard, rc)
|
||||
return err
|
||||
}); err != nil {
|
||||
if err := apply(ctx, mounts, rc); err != nil {
|
||||
return emptyDesc, err
|
||||
}
|
||||
|
||||
|
90
diff/apply/apply_linux.go
Normal file
90
diff/apply/apply_linux.go
Normal file
@ -0,0 +1,90 @@
|
||||
// +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/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, err := getOverlayPath(mounts[0].Options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = archive.Apply(ctx, path, r,
|
||||
archive.WithConvertWhiteout(archive.OverlayConvertWhiteout))
|
||||
return err
|
||||
case len(mounts) == 1 && mounts[0].Type == "aufs":
|
||||
path, err := getAufsPath(mounts[0].Options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = archive.Apply(ctx, path, r,
|
||||
archive.WithConvertWhiteout(archive.AufsConvertWhiteout))
|
||||
return err
|
||||
default:
|
||||
return mount.WithTempMount(ctx, mounts, func(root string) error {
|
||||
_, err := archive.Apply(ctx, root, r)
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getOverlayPath(options []string) (string, error) {
|
||||
const upperdirPrefix = "upperdir="
|
||||
for _, o := range options {
|
||||
if strings.HasPrefix(o, upperdirPrefix) {
|
||||
return strings.TrimPrefix(o, upperdirPrefix), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("upperdir not found")
|
||||
}
|
||||
|
||||
func getAufsPath(options []string) (string, error) {
|
||||
const (
|
||||
sep = ":"
|
||||
brPrefix1 = "br:"
|
||||
brPrefix2 = "br="
|
||||
rwSuffix = "=rw"
|
||||
)
|
||||
for _, o := range options {
|
||||
if strings.HasPrefix(o, brPrefix1) {
|
||||
o = strings.TrimPrefix(o, brPrefix1)
|
||||
} else if strings.HasPrefix(o, brPrefix2) {
|
||||
o = strings.TrimPrefix(o, brPrefix2)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
for _, b := range strings.Split(o, sep) {
|
||||
if strings.HasSuffix(b, rwSuffix) {
|
||||
return strings.TrimSuffix(b, rwSuffix), nil
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return "", errors.New("rw branch not found")
|
||||
}
|
79
diff/apply/apply_linux_test.go
Normal file
79
diff/apply/apply_linux_test.go
Normal file
@ -0,0 +1,79 @@
|
||||
// +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/lower", "workdir=/test/work"}
|
||||
path, err := getOverlayPath(good)
|
||||
if err != nil {
|
||||
t.Fatalf("Get overlay path failed: %v", err)
|
||||
}
|
||||
if path != "/test/upper" {
|
||||
t.Fatalf("Unexpected upperdir: %q", path)
|
||||
}
|
||||
|
||||
bad := []string{"lowerdir=/test/lower"}
|
||||
_, err = getOverlayPath(bad)
|
||||
if err == nil {
|
||||
t.Fatalf("An error is expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAufsPath(t *testing.T) {
|
||||
rwDir := "/test/rw"
|
||||
for _, test := range []struct {
|
||||
options []string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
options: []string{"random:option", "br:" + rwDir + "=rw:/test/ro=ro+wh"},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
options: []string{"random:option", "br=" + rwDir + "=rw:/test/ro=ro+wh"},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
options: []string{"random:option"},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
options: []string{"br:/test/ro=ro+wh"},
|
||||
expectErr: true,
|
||||
},
|
||||
} {
|
||||
path, 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 != rwDir {
|
||||
t.Fatalf("Unexpected rw dir: %q", path)
|
||||
}
|
||||
}
|
||||
}
|
34
diff/apply/apply_other.go
Normal file
34
diff/apply/apply_other.go
Normal file
@ -0,0 +1,34 @@
|
||||
// +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"
|
||||
|
||||
"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
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user