diff --git a/frontend/dockerfile/dockerfile2llb/epoch.go b/frontend/dockerfile/dockerfile2llb/epoch.go index 03e7d667d310..5cef1a8bcd63 100644 --- a/frontend/dockerfile/dockerfile2llb/epoch.go +++ b/frontend/dockerfile/dockerfile2llb/epoch.go @@ -21,8 +21,8 @@ import ( "github.com/moby/buildkit/frontend/dockerui" gwclient "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/solver/pb" + "github.com/moby/buildkit/util/archiveutil" "github.com/moby/buildkit/util/gitutil/gitobject" - archivecompression "github.com/moby/go-archive/compression" digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -348,7 +348,7 @@ func archiveMaxTimeFromRef(ctx context.Context, ref gwclient.Reference, filename if err != nil { return nil, err } - rc, err := archivecompression.DecompressStream(bytes.NewReader(dt)) + rc, err := archiveutil.DecompressStream(bytes.NewReader(dt)) if err != nil { if allowNonArchive { return nil, nil diff --git a/frontend/dockerui/context.go b/frontend/dockerui/context.go index 6fb3ebd1be60..78b47ab22a9d 100644 --- a/frontend/dockerui/context.go +++ b/frontend/dockerui/context.go @@ -19,8 +19,8 @@ import ( "github.com/moby/buildkit/frontend/gateway/client" gwpb "github.com/moby/buildkit/frontend/gateway/pb" "github.com/moby/buildkit/solver/pb" + "github.com/moby/buildkit/util/archiveutil" "github.com/moby/buildkit/util/gitutil/gitobject" - archivecompression "github.com/moby/go-archive/compression" "github.com/pkg/errors" ) @@ -224,7 +224,7 @@ func archiveMaxTimeFromHTTPArchive(ctx context.Context, bctx *buildContext) (*ti if err != nil { return nil, err } - rc, err := archivecompression.DecompressStream(bytes.NewReader(dt)) + rc, err := archiveutil.DecompressStream(bytes.NewReader(dt)) if err != nil { return nil, err } @@ -339,11 +339,9 @@ func isArchive(header []byte) bool { {0x42, 0x5A, 0x68}, // bzip2 {0x1F, 0x8B, 0x08}, // gzip {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, // xz + {0x28, 0xB5, 0x2F, 0xFD}, // zstd } { - if len(header) < len(m) { - continue - } - if bytes.Equal(m, header[:len(m)]) { + if bytes.HasPrefix(header, m) { return true } } diff --git a/go.mod b/go.mod index 89f3be5b0a0d..c90cf444efb6 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,6 @@ require ( github.com/in-toto/in-toto-golang v0.11.0 github.com/klauspost/compress v1.19.1 github.com/moby/docker-image-spec v1.3.1 - github.com/moby/go-archive v0.2.0 github.com/moby/locker v1.0.1 github.com/moby/patternmatcher v0.6.1 github.com/moby/policy-helpers v0.0.0-20260722051018-856be88baec4 @@ -195,7 +194,6 @@ require ( github.com/in-toto/attestation v1.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/moby/sys/capability v0.4.0 // indirect - github.com/moby/sys/mount v0.3.5 // indirect github.com/moby/sys/sequential v0.7.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/ulid/v2 v2.1.1 // indirect diff --git a/go.sum b/go.sum index 56fe52a74274..c10a54968f39 100644 --- a/go.sum +++ b/go.sum @@ -418,8 +418,6 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= -github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= @@ -430,8 +428,6 @@ github.com/moby/profiles/seccomp v0.2.3 h1:nrHNSiECQQvq4WjgceCUgJXIXUJBIswVQ133k github.com/moby/profiles/seccomp v0.2.3/go.mod h1:8m3qkkWZXrRsMqlUUN2zyccnYmBf3EAdQYPMVJ3NBhk= github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= -github.com/moby/sys/mount v0.3.5 h1:eS3fsZTjHaBihwjp4/+5Z3jxqLXYsbwxqpVSfFv3M00= -github.com/moby/sys/mount v0.3.5/go.mod h1:WUQDO+/uCiCIkIztx8SrwIDVn2dtMFRBebRhpDFT71M= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/reexec v0.1.0 h1:RrBi8e0EBTLEgfruBOFcxtElzRGTEUkeIFaVXgU7wok= @@ -779,8 +775,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= -gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= diff --git a/solver/llbsolver/file/backend.go b/solver/llbsolver/file/backend.go index be431a4f7c94..b560e89155e0 100644 --- a/solver/llbsolver/file/backend.go +++ b/solver/llbsolver/file/backend.go @@ -255,7 +255,7 @@ func docopy(ctx context.Context, src, dest string, action *pb.FileActionCopy, u for _, s := range m { if action.AttemptUnpackDockerCompatibility { - if ok, err := unpack(src, s, dest, destPath, ch, u, timestampToTime(action.Timestamp), idmap); err != nil { + if ok, err := unpack(ctx, src, s, dest, destPath, ch, u, timestampToTime(action.Timestamp), idmap); err != nil { return errors.WithStack(err) } else if ok { continue diff --git a/solver/llbsolver/file/unpack.go b/solver/llbsolver/file/unpack.go index de27705004c1..202580b5b109 100644 --- a/solver/llbsolver/file/unpack.go +++ b/solver/llbsolver/file/unpack.go @@ -2,18 +2,17 @@ package file import ( "archive/tar" + "context" "os" "time" "github.com/containerd/continuity/fs" - "github.com/moby/go-archive" - "github.com/moby/go-archive/chrootarchive" - "github.com/moby/go-archive/compression" + "github.com/moby/buildkit/util/archiveutil" "github.com/moby/sys/user" copy "github.com/tonistiigi/fsutil/copy" ) -func unpack(srcRoot string, src string, destRoot string, dest string, ch copy.Chowner, u *copy.User, tm *time.Time, idmap *user.IdentityMapping) (bool, error) { +func unpack(ctx context.Context, srcRoot string, src string, destRoot string, dest string, ch copy.Chowner, u *copy.User, tm *time.Time, idmap *user.IdentityMapping) (bool, error) { src, err := fs.RootPath(srcRoot, src) if err != nil { return false, err @@ -36,19 +35,14 @@ func unpack(srcRoot string, src string, destRoot string, dest string, ch copy.Ch } defer file.Close() - opts := &archive.TarOptions{ - BestEffortXattrs: true, - } - if idmap != nil { - opts.IDMap = *idmap - } - if u != nil { - opts.ChownOpts = &archive.ChownOpts{ - UID: u.UID, - GID: u.GID, - } + rdr, err := archiveutil.DecompressStream(file) + if err != nil { + return false, err } - return true, chrootarchive.Untar(file, dest, opts) + defer rdr.Close() + + err = applyRootArchive(ctx, dest, rdr, u, idmap, unpackNoSameOwner()) + return true, err } func isArchivePath(path string) bool { @@ -64,7 +58,7 @@ func isArchivePath(path string) bool { return false } defer file.Close() - rdr, err := compression.DecompressStream(file) + rdr, err := archiveutil.DecompressStream(file) if err != nil { return false } diff --git a/solver/llbsolver/file/unpack_root.go b/solver/llbsolver/file/unpack_root.go new file mode 100644 index 000000000000..def4024c4c9a --- /dev/null +++ b/solver/llbsolver/file/unpack_root.go @@ -0,0 +1,314 @@ +package file + +import ( + "archive/tar" + "context" + "io" + "os" + "path/filepath" + "strings" + "time" + + "github.com/moby/sys/user" + "github.com/pkg/errors" + copy "github.com/tonistiigi/fsutil/copy" +) + +type rootDirTime struct { + name string + atime time.Time + mtime time.Time +} + +func applyRootArchive(ctx context.Context, dest string, r io.Reader, u *copy.User, idmap *user.IdentityMapping, noSameOwner bool) error { + root, err := os.OpenRoot(dest) + if err != nil { + return err + } + defer root.Close() + + tr := tar.NewReader(r) + var dirs []rootDirTime + var copyBuf []byte + for { + select { + case <-ctx.Done(): + return context.Cause(ctx) + default: + } + + hdr, err := tr.Next() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return err + } + if hdr.Typeflag == tar.TypeXGlobalHeader || hdr.Typeflag == tar.TypeXHeader { + continue + } + + name, err := cleanRootTarPath(hdr.Name) + if err != nil { + return err + } + if name == "" { + continue + } + hdr.Name = name + + if err := mapArchiveHeaderOwner(hdr, u, idmap); err != nil { + return err + } + + opName, err := resolveRootPath(root, name, hdr.Typeflag == tar.TypeDir) + if err != nil { + return err + } + if opName == "" { + continue + } + + parent := filepath.Dir(opName) + if parent != "." { + if err := root.MkdirAll(parent, 0o755); err != nil { + return err + } + } + + fi, err := root.Lstat(opName) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + return err + } + } else if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { + if err := root.RemoveAll(opName); err != nil { + return err + } + } + + mode := hdr.FileInfo().Mode() + switch hdr.Typeflag { + case tar.TypeDir: + if err := root.Mkdir(opName, mode.Perm()); err != nil && !errors.Is(err, os.ErrExist) { + return err + } + if err := applyRootOwner(root, opName, hdr, noSameOwner); err != nil { + return err + } + if err := root.Chmod(opName, mode); err != nil { + return err + } + dirs = append(dirs, rootDirTime{ + name: opName, + atime: headerAccessTime(hdr), + mtime: hdr.ModTime, + }) + case tar.TypeReg, 0: + // A zero typeflag is the historic tar regular-file marker. + file, err := root.OpenFile(opName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode.Perm()) + if err != nil { + return err + } + if copyBuf == nil { + copyBuf = make([]byte, 32*1024) + } + for { + select { + case <-ctx.Done(): + file.Close() + return context.Cause(ctx) + default: + } + + nr, er := tr.Read(copyBuf) + if nr > 0 { + nw, ew := file.Write(copyBuf[:nr]) + if ew != nil { + file.Close() + return ew + } + if nw != nr { + file.Close() + return io.ErrShortWrite + } + } + if er == io.EOF { + break + } + if er != nil { + file.Close() + return er + } + } + if !noSameOwner { + if err := file.Chown(hdr.Uid, hdr.Gid); err != nil { + file.Close() + return err + } + } + if err := file.Chmod(mode); err != nil { + file.Close() + return err + } + if err := root.Chtimes(opName, headerAccessTime(hdr), hdr.ModTime); err != nil { + file.Close() + return err + } + if err := file.Close(); err != nil { + return err + } + case tar.TypeSymlink: + // Preserve the archive's link target. os.Root does not validate the + // target here. Later extraction resolves absolute targets relative to + // the extraction root and rejects relative targets that escape it. + if err := root.Symlink(hdr.Linkname, opName); err != nil { + return err + } + if err := applyRootOwner(root, opName, hdr, noSameOwner); err != nil { + return err + } + case tar.TypeLink: + linkName, err := cleanRootTarPath(hdr.Linkname) + if err != nil { + return err + } + if linkName == "" { + return errors.Errorf("archive hardlink %q targets extraction root", hdr.Name) + } + linkName, err = resolveRootPath(root, linkName, false) + if err != nil { + return err + } + if err := root.Link(linkName, opName); err != nil { + return err + } + if err := applyRootOwner(root, opName, hdr, noSameOwner); err != nil { + return err + } + fi, err := root.Lstat(opName) + if err != nil { + return err + } + if fi.Mode()&os.ModeSymlink == 0 { + if err := root.Chmod(opName, mode); err != nil { + return err + } + if err := root.Chtimes(opName, headerAccessTime(hdr), hdr.ModTime); err != nil { + return err + } + } + default: + return errors.Errorf("unsupported tar entry %q type %d", hdr.Name, hdr.Typeflag) + } + } + + for i := len(dirs) - 1; i >= 0; i-- { + if err := root.Chtimes(dirs[i].name, dirs[i].atime, dirs[i].mtime); err != nil { + return err + } + } + return nil +} + +func cleanRootTarPath(name string) (string, error) { + name = filepath.Clean(name) + if filepath.VolumeName(name) != "" { + return "", errors.Errorf("archive path %q is outside extraction root", name) + } + // Match chroot-style extraction: /foo is extracted as foo under the + // destination root, while paths that still escape are rejected below. + name = strings.TrimLeft(name, string(filepath.Separator)) + if name == "." || name == "" { + return "", nil + } + if !filepath.IsLocal(name) { + return "", errors.Errorf("archive path %q is outside extraction root", name) + } + return name, nil +} + +func resolveRootPath(root *os.Root, name string, followLeaf bool) (string, error) { + original := name + for range 255 { + parts := strings.Split(name, string(filepath.Separator)) + resolved := make([]string, 0, len(parts)) + followed := false + for i, part := range parts { + if part == "" || part == "." { + continue + } + candidate := filepath.Join(append(resolved, part)...) + if i == len(parts)-1 && !followLeaf { + resolved = append(resolved, part) + continue + } + fi, err := root.Lstat(candidate) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + resolved = append(resolved, parts[i:]...) + return filepath.Join(resolved...), nil + } + return "", err + } + if fi.Mode()&os.ModeSymlink == 0 { + resolved = append(resolved, part) + continue + } + + target, err := root.Readlink(candidate) + if err != nil { + return "", err + } + if filepath.IsAbs(target) || filepath.VolumeName(target) != "" { + name, err = cleanRootTarPath(target) + if err != nil { + return "", err + } + } else { + name = filepath.Clean(filepath.Join(filepath.Dir(candidate), target)) + if name == "." { + name = "" + } + if name != "" && !filepath.IsLocal(name) { + return "", errors.Errorf("archive symlink %q points outside extraction root", candidate) + } + } + remaining := filepath.Join(parts[i+1:]...) + if remaining != "" { + name = filepath.Join(name, remaining) + } + followed = true + break + } + if !followed { + return filepath.Join(resolved...), nil + } + } + return "", errors.Errorf("too many symlinks resolving archive path %q", original) +} + +func headerAccessTime(hdr *tar.Header) time.Time { + if !hdr.AccessTime.IsZero() { + return hdr.AccessTime + } + return hdr.ModTime +} + +func mapArchiveHeaderOwner(hdr *tar.Header, u *copy.User, idmap *user.IdentityMapping) error { + uid, gid := hdr.Uid, hdr.Gid + // Match go-archive behavior: remap archive header IDs first, then let + // explicit --chown values override the header ownership. + if idmap != nil { + var err error + uid, gid, err = idmap.ToHost(uid, gid) + if err != nil { + return err + } + } + if u != nil { + uid, gid = u.UID, u.GID + } + hdr.Uid, hdr.Gid = uid, gid + return nil +} diff --git a/solver/llbsolver/file/unpack_test.go b/solver/llbsolver/file/unpack_test.go new file mode 100644 index 000000000000..ef60bfa7f87b --- /dev/null +++ b/solver/llbsolver/file/unpack_test.go @@ -0,0 +1,141 @@ +package file + +import ( + "archive/tar" + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/moby/sys/user" + "github.com/stretchr/testify/require" + copy "github.com/tonistiigi/fsutil/copy" +) + +func TestOwnerMapperMapsArchiveHeader(t *testing.T) { + t.Parallel() + + idmap := &user.IdentityMapping{ + UIDMaps: []user.IDMap{{ID: 0, ParentID: 1000, Count: 10}}, + GIDMaps: []user.IDMap{{ID: 0, ParentID: 2000, Count: 10}}, + } + hdr := &tar.Header{Uid: 2, Gid: 3} + + err := mapArchiveHeaderOwner(hdr, nil, idmap) + require.NoError(t, err) + require.Equal(t, 1002, hdr.Uid) + require.Equal(t, 2003, hdr.Gid) +} + +func TestOwnerMapperChownOverridesArchiveHeader(t *testing.T) { + t.Parallel() + + idmap := &user.IdentityMapping{ + UIDMaps: []user.IDMap{{ID: 0, ParentID: 1000, Count: 10}}, + GIDMaps: []user.IDMap{{ID: 0, ParentID: 2000, Count: 10}}, + } + hdr := &tar.Header{Uid: 2, Gid: 3} + + err := mapArchiveHeaderOwner(hdr, ©.User{UID: 5, GID: 6}, idmap) + require.NoError(t, err) + require.Equal(t, 5, hdr.Uid) + require.Equal(t, 6, hdr.Gid) +} + +func TestUnpackPreservesWhiteoutFiles(t *testing.T) { + dest := t.TempDir() + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, "victim", "victim") + writeTarFile(t, tw, ".wh.victim", "whiteout") + require.NoError(t, tw.Close()) + + require.NoError(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + dt, err := os.ReadFile(filepath.Join(dest, "victim")) + require.NoError(t, err) + require.Equal(t, "victim", string(dt)) + + dt, err = os.ReadFile(filepath.Join(dest, ".wh.victim")) + require.NoError(t, err) + require.Equal(t, "whiteout", string(dt)) +} + +func TestUnpackRejectsParentDirectoryPath(t *testing.T) { + parent := t.TempDir() + dest := filepath.Join(parent, "dest") + outside := filepath.Join(parent, "outside") + require.NoError(t, os.Mkdir(dest, 0o755)) + require.NoError(t, os.Mkdir(outside, 0o755)) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, "../outside/pwned", "pwned") + require.NoError(t, tw.Close()) + + require.Error(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + _, err := os.Stat(filepath.Join(outside, "pwned")) + require.True(t, os.IsNotExist(err), "archive path escaped destination") +} + +func TestUnpackAbsolutePathIsRelativeToDestination(t *testing.T) { + dest := t.TempDir() + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, "/absolute", "content") + require.NoError(t, tw.Close()) + + require.NoError(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + dt, err := os.ReadFile(filepath.Join(dest, "absolute")) + require.NoError(t, err) + require.Equal(t, "content", string(dt)) +} + +func TestUnpackDoesNotHardlinkOutsideDestination(t *testing.T) { + parent := t.TempDir() + dest := filepath.Join(parent, "dest") + outside := filepath.Join(parent, "outside") + require.NoError(t, os.Mkdir(dest, 0o755)) + require.NoError(t, os.Mkdir(outside, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(outside, "target"), []byte("target"), 0o644)) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + require.NoError(t, tw.WriteHeader(&tar.Header{ + Name: "hardlink", + Typeflag: tar.TypeLink, + Linkname: filepath.ToSlash(filepath.Join(outside, "target")), + Mode: 0o644, + })) + require.NoError(t, tw.Close()) + + require.Error(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + dt, err := os.ReadFile(filepath.Join(outside, "target")) + require.NoError(t, err) + require.Equal(t, "target", string(dt)) +} + +func writeTarFile(t *testing.T, tw *tar.Writer, name, content string) { + t.Helper() + + err := tw.WriteHeader(&tar.Header{ + Name: name, + Typeflag: tar.TypeReg, + Mode: 0o644, + Size: int64(len(content)), + }) + require.NoError(t, err) + _, err = tw.Write([]byte(content)) + require.NoError(t, err) +} + +func applyArchiveNoSameOwner(t *testing.T, dest string, dt []byte) error { + t.Helper() + + return applyRootArchive(t.Context(), dest, bytes.NewReader(dt), nil, nil, true) +} diff --git a/solver/llbsolver/file/unpack_unix.go b/solver/llbsolver/file/unpack_unix.go new file mode 100644 index 000000000000..2fc52f949737 --- /dev/null +++ b/solver/llbsolver/file/unpack_unix.go @@ -0,0 +1,19 @@ +//go:build !windows + +package file + +import ( + "archive/tar" + "os" +) + +func unpackNoSameOwner() bool { + return false +} + +func applyRootOwner(root *os.Root, name string, hdr *tar.Header, noSameOwner bool) error { + if noSameOwner { + return nil + } + return root.Lchown(name, hdr.Uid, hdr.Gid) +} diff --git a/solver/llbsolver/file/unpack_unix_test.go b/solver/llbsolver/file/unpack_unix_test.go new file mode 100644 index 000000000000..8efbc50e102c --- /dev/null +++ b/solver/llbsolver/file/unpack_unix_test.go @@ -0,0 +1,119 @@ +//go:build !windows + +package file + +import ( + "archive/tar" + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUnpackWritesThroughRootLocalAbsoluteSymlink(t *testing.T) { + dest := t.TempDir() + require.NoError(t, os.Mkdir(filepath.Join(dest, "run"), 0o755)) + require.NoError(t, os.Mkdir(filepath.Join(dest, "var"), 0o755)) + require.NoError(t, os.Symlink("/run", filepath.Join(dest, "var", "run"))) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + require.NoError(t, tw.WriteHeader(&tar.Header{ + Name: "var/run/act", + Typeflag: tar.TypeDir, + Mode: 0o755, + })) + writeTarFile(t, tw, "var/run/act/file", "content") + require.NoError(t, tw.WriteHeader(&tar.Header{ + Name: "var/run/act/file.link", + Typeflag: tar.TypeLink, + Linkname: "var/run/act/file", + Mode: 0o644, + })) + require.NoError(t, tw.Close()) + + require.NoError(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + dt, err := os.ReadFile(filepath.Join(dest, "run", "act", "file")) + require.NoError(t, err) + require.Equal(t, "content", string(dt)) + + link, err := os.Readlink(filepath.Join(dest, "var", "run")) + require.NoError(t, err) + require.Equal(t, "/run", link) + + fileInfo, err := os.Stat(filepath.Join(dest, "run", "act", "file")) + require.NoError(t, err) + linkInfo, err := os.Stat(filepath.Join(dest, "run", "act", "file.link")) + require.NoError(t, err) + require.True(t, os.SameFile(fileInfo, linkInfo)) +} + +func TestUnpackDoesNotWriteThroughAbsoluteArchiveSymlink(t *testing.T) { + parent := t.TempDir() + dest := filepath.Join(parent, "dest") + outside := filepath.Join(parent, "outside") + require.NoError(t, os.Mkdir(dest, 0o755)) + require.NoError(t, os.Mkdir(outside, 0o755)) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + require.NoError(t, tw.WriteHeader(&tar.Header{ + Name: "escape", + Typeflag: tar.TypeSymlink, + Linkname: filepath.ToSlash(outside), + Mode: 0o777, + })) + writeTarFile(t, tw, "escape/pwned", "pwned") + require.NoError(t, tw.Close()) + + require.NoError(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + _, err := os.Stat(filepath.Join(outside, "pwned")) + require.True(t, os.IsNotExist(err), "archive-created symlink escaped destination") +} + +func TestUnpackRejectsRelativeArchiveSymlinkEscape(t *testing.T) { + parent := t.TempDir() + dest := filepath.Join(parent, "dest") + outside := filepath.Join(parent, "outside") + require.NoError(t, os.Mkdir(dest, 0o755)) + require.NoError(t, os.Mkdir(outside, 0o755)) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + require.NoError(t, tw.WriteHeader(&tar.Header{ + Name: "escape", + Typeflag: tar.TypeSymlink, + Linkname: "../outside", + Mode: 0o777, + })) + writeTarFile(t, tw, "escape/pwned", "pwned") + require.NoError(t, tw.Close()) + + require.Error(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + _, err := os.Stat(filepath.Join(outside, "pwned")) + require.True(t, os.IsNotExist(err), "archive-created symlink escaped destination") +} + +func TestUnpackRejectsRelativePreexistingSymlinkEscape(t *testing.T) { + parent := t.TempDir() + dest := filepath.Join(parent, "dest") + outside := filepath.Join(parent, "outside") + require.NoError(t, os.Mkdir(dest, 0o755)) + require.NoError(t, os.Mkdir(outside, 0o755)) + require.NoError(t, os.Symlink("../outside", filepath.Join(dest, "escape"))) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, "escape/pwned", "pwned") + require.NoError(t, tw.Close()) + + require.Error(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + _, err := os.Stat(filepath.Join(outside, "pwned")) + require.True(t, os.IsNotExist(err), "preexisting symlink escaped destination") +} diff --git a/solver/llbsolver/file/unpack_windows.go b/solver/llbsolver/file/unpack_windows.go new file mode 100644 index 000000000000..b18b9aa1e329 --- /dev/null +++ b/solver/llbsolver/file/unpack_windows.go @@ -0,0 +1,16 @@ +//go:build windows + +package file + +import ( + "archive/tar" + "os" +) + +func unpackNoSameOwner() bool { + return true +} + +func applyRootOwner(*os.Root, string, *tar.Header, bool) error { + return nil +} diff --git a/solver/llbsolver/file/unpack_windows_test.go b/solver/llbsolver/file/unpack_windows_test.go new file mode 100644 index 000000000000..61eb005753ad --- /dev/null +++ b/solver/llbsolver/file/unpack_windows_test.go @@ -0,0 +1,49 @@ +//go:build windows + +package file + +import ( + "archive/tar" + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUnpackSkipsSameOwnerOnWindows(t *testing.T) { + srcRoot := t.TempDir() + destRoot := t.TempDir() + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, "foo", "content") + require.NoError(t, tw.Close()) + + require.NoError(t, os.WriteFile(filepath.Join(srcRoot, "archive.tar"), buf.Bytes(), 0o600)) + + ok, err := unpack(t.Context(), srcRoot, "archive.tar", destRoot, "/", nil, nil, nil, nil) + require.NoError(t, err) + require.True(t, ok) + + dt, err := os.ReadFile(filepath.Join(destRoot, "foo")) + require.NoError(t, err) + require.Equal(t, "content", string(dt)) +} + +func TestUnpackRejectsWindowsVolumePath(t *testing.T) { + parent := t.TempDir() + dest := filepath.Join(parent, "dest") + require.NoError(t, os.Mkdir(dest, 0o755)) + + buf := bytes.NewBuffer(nil) + tw := tar.NewWriter(buf) + writeTarFile(t, tw, filepath.VolumeName(parent)+`/pwned`, "pwned") + require.NoError(t, tw.Close()) + + require.Error(t, applyArchiveNoSameOwner(t, dest, buf.Bytes())) + + _, err := os.Stat(filepath.Join(dest, "pwned")) + require.True(t, os.IsNotExist(err), "volume-qualified archive path was extracted") +} diff --git a/util/archiveutil/compression.go b/util/archiveutil/compression.go new file mode 100644 index 000000000000..96f48483ab25 --- /dev/null +++ b/util/archiveutil/compression.go @@ -0,0 +1,100 @@ +package archiveutil + +import ( + "bufio" + "bytes" + "compress/bzip2" + "context" + "io" + "os/exec" + + cdcompression "github.com/containerd/containerd/v2/pkg/archive/compression" + "github.com/pkg/errors" +) + +var ( + // bzip2 streams start with ASCII "BZh". + // See https://www.loc.gov/preservation/digital/formats/fdd/fdd000600.shtml + bzip2Magic = []byte{0x42, 0x5a, 0x68} + + // XZ streams start with these six header magic bytes. + // See https://tukaani.org/xz/xz-file-format.txt + xzMagic = []byte{0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00} +) + +type readCloser struct { + io.Reader + close func() error +} + +func (r readCloser) Close() error { + if r.close != nil { + return r.close() + } + return nil +} + +// DecompressStream decompresses Docker-compatible archive streams. +func DecompressStream(r io.Reader) (io.ReadCloser, error) { + buf := bufio.NewReaderSize(r, 32*1024) + bs, err := buf.Peek(10) + if err != nil && err != io.EOF { + return nil, err + } + + switch { + case bytes.HasPrefix(bs, bzip2Magic): + return readCloser{Reader: bzip2.NewReader(buf)}, nil + case bytes.HasPrefix(bs, xzMagic): + ctx, cancel := context.WithCancelCause(context.Background()) + xzReader, err := cmdStream(exec.CommandContext(ctx, "xz", "-d", "-c", "-q"), buf) + if err != nil { + cancel(err) + return nil, err + } + return readCloser{ + Reader: xzReader, + close: func() error { + cancel(nil) + return xzReader.Close() + }, + }, nil + default: + return cdcompression.DecompressStream(buf) + } +} + +func cmdStream(cmd *exec.Cmd, in io.Reader) (io.ReadCloser, error) { + reader, writer := io.Pipe() + cmd.Stdin = in + cmd.Stdout = writer + + var errBuf bytes.Buffer + cmd.Stderr = &errBuf + + if err := cmd.Start(); err != nil { + return nil, err + } + + done := make(chan struct{}) + go func() { + defer close(done) + if err := cmd.Wait(); err != nil { + if errBuf.Len() > 0 { + err = errors.Wrapf(err, "%s", errBuf.String()) + } + writer.CloseWithError(err) + } else { + writer.Close() + } + }() + + return readCloser{ + Reader: reader, + close: func() error { + err := reader.Close() + <-done + return err + }, + }, nil +} diff --git a/util/archiveutil/compression_test.go b/util/archiveutil/compression_test.go new file mode 100644 index 000000000000..79c2d4b2238a --- /dev/null +++ b/util/archiveutil/compression_test.go @@ -0,0 +1,97 @@ +package archiveutil + +import ( + "bytes" + "compress/gzip" + "io" + "os/exec" + "testing" + + "github.com/klauspost/compress/zstd" + "github.com/stretchr/testify/require" +) + +func TestDecompressStream(t *testing.T) { + t.Parallel() + + gz := bytes.NewBuffer(nil) + gzw := gzip.NewWriter(gz) + _, err := gzw.Write([]byte("payload")) + require.NoError(t, err) + require.NoError(t, gzw.Close()) + + zstdBuf := bytes.NewBuffer(nil) + zstdw, err := zstd.NewWriter(zstdBuf) + require.NoError(t, err) + _, err = zstdw.Write([]byte("payload")) + require.NoError(t, err) + require.NoError(t, zstdw.Close()) + + tests := []struct { + name string + dt []byte + }{ + { + name: "uncompressed", + dt: []byte("payload"), + }, + { + name: "gzip", + dt: gz.Bytes(), + }, + { + name: "bzip2", + dt: []byte{ + 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, + 0x53, 0x59, 0x1d, 0x1b, 0x8a, 0x0d, 0x00, 0x00, + 0x02, 0x81, 0x80, 0x24, 0x04, 0xc0, 0x20, 0x20, + 0x00, 0x22, 0x18, 0x68, 0x30, 0x09, 0x58, 0x13, + 0x0b, 0xb9, 0x22, 0x9c, 0x28, 0x48, 0x0e, 0x8d, + 0xc5, 0x06, 0x80, + }, + }, + { + name: "zstd", + dt: zstdBuf.Bytes(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + rc, err := DecompressStream(bytes.NewReader(tt.dt)) + require.NoError(t, err) + defer rc.Close() + + dt, err := io.ReadAll(rc) + require.NoError(t, err) + require.Equal(t, "payload", string(dt)) + }) + } +} + +func TestDecompressStreamXz(t *testing.T) { + t.Parallel() + + if _, err := exec.LookPath("xz"); err != nil { + t.Skip("xz binary is not available") + } + + rc, err := DecompressStream(bytes.NewReader([]byte{ + 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x04, + 0xe6, 0xd6, 0xb4, 0x46, 0x02, 0x00, 0x21, 0x01, + 0x16, 0x00, 0x00, 0x00, 0x74, 0x2f, 0xe5, 0xa3, + 0x01, 0x00, 0x06, 0x70, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x00, 0x00, 0x8b, 0x1f, 0xd0, 0x83, + 0x9e, 0x49, 0x8f, 0x37, 0x00, 0x01, 0x1f, 0x07, + 0x16, 0x2e, 0xb8, 0x73, 0x1f, 0xb6, 0xf3, 0x7d, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x59, 0x5a, + })) + require.NoError(t, err) + defer rc.Close() + + dt, err := io.ReadAll(rc) + require.NoError(t, err) + require.Equal(t, "payload", string(dt)) +} diff --git a/vendor/github.com/moby/go-archive/.gitattributes b/vendor/github.com/moby/go-archive/.gitattributes deleted file mode 100644 index 4bb139d900bd..000000000000 --- a/vendor/github.com/moby/go-archive/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -*.go -text diff=golang -*.go text eol=lf diff --git a/vendor/github.com/moby/go-archive/.gitignore b/vendor/github.com/moby/go-archive/.gitignore deleted file mode 100644 index 3f2bc47416e7..000000000000 --- a/vendor/github.com/moby/go-archive/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/coverage.txt diff --git a/vendor/github.com/moby/go-archive/.golangci.yml b/vendor/github.com/moby/go-archive/.golangci.yml deleted file mode 100644 index 21439e5c6951..000000000000 --- a/vendor/github.com/moby/go-archive/.golangci.yml +++ /dev/null @@ -1,33 +0,0 @@ -version: "2" - -issues: - # Disable maximum issues count per one linter. - max-issues-per-linter: 0 - # Disable maximum count of issues with the same text. - max-same-issues: 0 - -linters: - enable: - - errorlint - - unconvert - - unparam - exclusions: - generated: disable - presets: - - comments - - std-error-handling - settings: - staticcheck: - # Enable all options, with some exceptions. - # For defaults, see https://golangci-lint.run/usage/linters/#staticcheck - checks: - - all - - -QF1008 # Omit embedded fields from selector expression; https://staticcheck.dev/docs/checks/#QF1008 - - -ST1003 # Poorly chosen identifier; https://staticcheck.dev/docs/checks/#ST1003 - -formatters: - enable: - - gofumpt - - goimports - exclusions: - generated: disable diff --git a/vendor/github.com/moby/go-archive/LICENSE b/vendor/github.com/moby/go-archive/LICENSE deleted file mode 100644 index d64569567334..000000000000 --- a/vendor/github.com/moby/go-archive/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/vendor/github.com/moby/go-archive/archive.go b/vendor/github.com/moby/go-archive/archive.go deleted file mode 100644 index 8dce2e6e2afa..000000000000 --- a/vendor/github.com/moby/go-archive/archive.go +++ /dev/null @@ -1,1129 +0,0 @@ -// Package archive provides helper functions for dealing with archive files. -package archive - -import ( - "archive/tar" - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "runtime" - "strings" - "syscall" - "time" - - "github.com/containerd/log" - "github.com/moby/patternmatcher" - "github.com/moby/sys/sequential" - "github.com/moby/sys/user" - - "github.com/moby/go-archive/compression" - "github.com/moby/go-archive/tarheader" -) - -// ImpliedDirectoryMode represents the mode (Unix permissions) applied to directories that are implied by files in a -// tar, but that do not have their own header entry. -// -// The permissions mask is stored in a constant instead of locally to ensure that magic numbers do not -// proliferate in the codebase. The default value 0755 has been selected based on the default umask of 0022, and -// a convention of mkdir(1) calling mkdir(2) with permissions of 0777, resulting in a final value of 0755. -// -// This value is currently implementation-defined, and not captured in any cross-runtime specification. Thus, it is -// subject to change in Moby at any time -- image authors who require consistent or known directory permissions -// should explicitly control them by ensuring that header entries exist for any applicable path. -const ImpliedDirectoryMode = 0o755 - -type ( - // WhiteoutFormat is the format of whiteouts unpacked - WhiteoutFormat int - - ChownOpts struct { - UID int - GID int - } - - // TarOptions wraps the tar options. - TarOptions struct { - IncludeFiles []string - ExcludePatterns []string - Compression compression.Compression - NoLchown bool - IDMap user.IdentityMapping - ChownOpts *ChownOpts - IncludeSourceDir bool - // WhiteoutFormat is the expected on disk format for whiteout files. - // This format will be converted to the standard format on pack - // and from the standard format on unpack. - WhiteoutFormat WhiteoutFormat - // When unpacking, specifies whether overwriting a directory with a - // non-directory is allowed and vice versa. - NoOverwriteDirNonDir bool - // For each include when creating an archive, the included name will be - // replaced with the matching name from this map. - RebaseNames map[string]string - InUserNS bool - // Allow unpacking to succeed in spite of failures to set extended - // attributes on the unpacked files due to the destination filesystem - // not supporting them or a lack of permissions. Extended attributes - // were probably in the archive for a reason, so set this option at - // your own peril. - BestEffortXattrs bool - } -) - -// Archiver implements the Archiver interface and allows the reuse of most utility functions of -// this package with a pluggable Untar function. Also, to facilitate the passing of specific id -// mappings for untar, an Archiver can be created with maps which will then be passed to Untar operations. -type Archiver struct { - Untar func(io.Reader, string, *TarOptions) error - IDMapping user.IdentityMapping -} - -// NewDefaultArchiver returns a new Archiver without any IdentityMapping -func NewDefaultArchiver() *Archiver { - return &Archiver{Untar: Untar} -} - -// breakoutError is used to differentiate errors related to breaking out -// When testing archive breakout in the unit tests, this error is expected -// in order for the test to pass. -type breakoutError error - -const ( - AUFSWhiteoutFormat WhiteoutFormat = 0 // AUFSWhiteoutFormat is the default format for whiteouts - OverlayWhiteoutFormat WhiteoutFormat = 1 // OverlayWhiteoutFormat formats whiteout according to the overlay standard. -) - -// IsArchivePath checks if the (possibly compressed) file at the given path -// starts with a tar file header. -func IsArchivePath(path string) bool { - file, err := os.Open(path) - if err != nil { - return false - } - defer file.Close() - rdr, err := compression.DecompressStream(file) - if err != nil { - return false - } - defer rdr.Close() - r := tar.NewReader(rdr) - _, err = r.Next() - return err == nil -} - -// TarModifierFunc is a function that can be passed to ReplaceFileTarWrapper to -// modify the contents or header of an entry in the archive. If the file already -// exists in the archive the TarModifierFunc will be called with the Header and -// a reader which will return the files content. If the file does not exist both -// header and content will be nil. -type TarModifierFunc func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) - -// ReplaceFileTarWrapper converts inputTarStream to a new tar stream. Files in the -// tar stream are modified if they match any of the keys in mods. -func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModifierFunc) io.ReadCloser { - pipeReader, pipeWriter := io.Pipe() - - go func() { - tarReader := tar.NewReader(inputTarStream) - tarWriter := tar.NewWriter(pipeWriter) - defer inputTarStream.Close() - defer tarWriter.Close() - - modify := func(name string, original *tar.Header, modifier TarModifierFunc, tarReader io.Reader) error { - header, data, err := modifier(name, original, tarReader) - switch { - case err != nil: - return err - case header == nil: - return nil - } - - if header.Name == "" { - header.Name = name - } - header.Size = int64(len(data)) - if err := tarWriter.WriteHeader(header); err != nil { - return err - } - if len(data) != 0 { - if _, err := tarWriter.Write(data); err != nil { - return err - } - } - return nil - } - - var err error - var originalHeader *tar.Header - for { - originalHeader, err = tarReader.Next() - if errors.Is(err, io.EOF) { - break - } - if err != nil { - pipeWriter.CloseWithError(err) - return - } - - modifier, ok := mods[originalHeader.Name] - if !ok { - // No modifiers for this file, copy the header and data - if err := tarWriter.WriteHeader(originalHeader); err != nil { - pipeWriter.CloseWithError(err) - return - } - if err := copyWithBuffer(tarWriter, tarReader); err != nil { - pipeWriter.CloseWithError(err) - return - } - continue - } - delete(mods, originalHeader.Name) - - if err := modify(originalHeader.Name, originalHeader, modifier, tarReader); err != nil { - pipeWriter.CloseWithError(err) - return - } - } - - // Apply the modifiers that haven't matched any files in the archive - for name, modifier := range mods { - if err := modify(name, nil, modifier, nil); err != nil { - pipeWriter.CloseWithError(err) - return - } - } - - pipeWriter.Close() - }() - return pipeReader -} - -// FileInfoHeader creates a populated Header from fi. -// -// Compared to the archive/tar package, this function fills in less information -// but is safe to call from a chrooted process. The AccessTime and ChangeTime -// fields are not set in the returned header, ModTime is truncated to one-second -// precision, and the Uname and Gname fields are only set when fi is a FileInfo -// value returned from tar.Header.FileInfo(). -func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, error) { - hdr, err := tarheader.FileInfoHeaderNoLookups(fi, link) - if err != nil { - return nil, err - } - hdr.Format = tar.FormatPAX - hdr.ModTime = hdr.ModTime.Truncate(time.Second) - hdr.AccessTime = time.Time{} - hdr.ChangeTime = time.Time{} - hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) - hdr.Name = canonicalTarName(name, fi.IsDir()) - return hdr, nil -} - -const paxSchilyXattr = "SCHILY.xattr." - -// ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem -// to a tar header -func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { - const ( - // Values based on linux/include/uapi/linux/capability.h - xattrCapsSz2 = 20 - versionOffset = 3 - vfsCapRevision2 = 2 - vfsCapRevision3 = 3 - ) - capability, _ := lgetxattr(path, "security.capability") - if capability != nil { - if capability[versionOffset] == vfsCapRevision3 { - // Convert VFS_CAP_REVISION_3 to VFS_CAP_REVISION_2 as root UID makes no - // sense outside the user namespace the archive is built in. - capability[versionOffset] = vfsCapRevision2 - capability = capability[:xattrCapsSz2] - } - if hdr.PAXRecords == nil { - hdr.PAXRecords = make(map[string]string) - } - hdr.PAXRecords[paxSchilyXattr+"security.capability"] = string(capability) - } - return nil -} - -type tarWhiteoutConverter interface { - ConvertWrite(*tar.Header, string, os.FileInfo) (*tar.Header, error) - ConvertRead(*tar.Header, string) (bool, error) -} - -type tarAppender struct { - TarWriter *tar.Writer - - // for hardlink mapping - SeenFiles map[uint64]string - IdentityMapping user.IdentityMapping - ChownOpts *ChownOpts - - // For packing and unpacking whiteout files in the - // non standard format. The whiteout files defined - // by the AUFS standard are used as the tar whiteout - // standard. - WhiteoutConverter tarWhiteoutConverter -} - -func newTarAppender(idMapping user.IdentityMapping, writer io.Writer, chownOpts *ChownOpts) *tarAppender { - return &tarAppender{ - SeenFiles: make(map[uint64]string), - TarWriter: tar.NewWriter(writer), - IdentityMapping: idMapping, - ChownOpts: chownOpts, - } -} - -// canonicalTarName provides a platform-independent and consistent POSIX-style -// path for files and directories to be archived regardless of the platform. -func canonicalTarName(name string, isDir bool) string { - name = filepath.ToSlash(name) - - // suffix with '/' for directories - if isDir && !strings.HasSuffix(name, "/") { - name += "/" - } - return name -} - -// addTarFile adds to the tar archive a file from `path` as `name` -func (ta *tarAppender) addTarFile(path, name string) error { - fi, err := os.Lstat(path) - if err != nil { - return err - } - - var link string - if fi.Mode()&os.ModeSymlink != 0 { - var err error - link, err = os.Readlink(path) - if err != nil { - return err - } - } - - hdr, err := FileInfoHeader(name, fi, link) - if err != nil { - return err - } - if err := ReadSecurityXattrToTarHeader(path, hdr); err != nil { - return err - } - - // if it's not a directory and has more than 1 link, - // it's hard linked, so set the type flag accordingly - if !fi.IsDir() && hasHardlinks(fi) { - inode, err := getInodeFromStat(fi.Sys()) - if err != nil { - return err - } - // a link should have a name that it links too - // and that linked name should be first in the tar archive - if oldpath, ok := ta.SeenFiles[inode]; ok { - hdr.Typeflag = tar.TypeLink - hdr.Linkname = oldpath - hdr.Size = 0 // This Must be here for the writer math to add up! - } else { - ta.SeenFiles[inode] = name - } - } - - // check whether the file is overlayfs whiteout - // if yes, skip re-mapping container ID mappings. - isOverlayWhiteout := fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 - - // handle re-mapping container ID mappings back to host ID mappings before - // writing tar headers/files. We skip whiteout files because they were written - // by the kernel and already have proper ownership relative to the host - if !isOverlayWhiteout && !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && !ta.IdentityMapping.Empty() { - uid, gid, err := getFileUIDGID(fi.Sys()) - if err != nil { - return err - } - hdr.Uid, hdr.Gid, err = ta.IdentityMapping.ToContainer(uid, gid) - if err != nil { - return err - } - } - - // explicitly override with ChownOpts - if ta.ChownOpts != nil { - hdr.Uid = ta.ChownOpts.UID - hdr.Gid = ta.ChownOpts.GID - } - - if ta.WhiteoutConverter != nil { - wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi) - if err != nil { - return err - } - - // If a new whiteout file exists, write original hdr, then - // replace hdr with wo to be written after. Whiteouts should - // always be written after the original. Note the original - // hdr may have been updated to be a whiteout with returning - // a whiteout header - if wo != nil { - if err := ta.TarWriter.WriteHeader(hdr); err != nil { - return err - } - if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { - return fmt.Errorf("tar: cannot use whiteout for non-empty file") - } - hdr = wo - } - } - - if err := ta.TarWriter.WriteHeader(hdr); err != nil { - return err - } - - if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { - // We use sequential file access to avoid depleting the standby list on - // Windows. On Linux, this equates to a regular os.Open. - file, err := sequential.Open(path) - if err != nil { - return err - } - - err = copyWithBuffer(ta.TarWriter, file) - file.Close() - if err != nil { - return err - } - } - - return nil -} - -func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error { - var ( - Lchown = true - inUserns, bestEffortXattrs bool - chownOpts *ChownOpts - ) - - // TODO(thaJeztah): make opts a required argument. - if opts != nil { - Lchown = !opts.NoLchown - inUserns = opts.InUserNS // TODO(thaJeztah): consider deprecating opts.InUserNS and detect locally. - chownOpts = opts.ChownOpts - bestEffortXattrs = opts.BestEffortXattrs - } - - // hdr.Mode is in linux format, which we can use for sycalls, - // but for os.Foo() calls we need the mode converted to os.FileMode, - // so use hdrInfo.Mode() (they differ for e.g. setuid bits) - hdrInfo := hdr.FileInfo() - - switch hdr.Typeflag { - case tar.TypeDir: - // Create directory unless it exists as a directory already. - // In that case we just want to merge the two - if fi, err := os.Lstat(path); err != nil || !fi.IsDir() { - if err := os.Mkdir(path, hdrInfo.Mode()); err != nil { - return err - } - } - - case tar.TypeReg: - // Source is regular file. We use sequential file access to avoid depleting - // the standby list on Windows. On Linux, this equates to a regular os.OpenFile. - file, err := sequential.OpenFile(path, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode()) - if err != nil { - return err - } - if err := copyWithBuffer(file, reader); err != nil { - _ = file.Close() - return err - } - _ = file.Close() - - case tar.TypeBlock, tar.TypeChar: - if inUserns { // cannot create devices in a userns - log.G(context.TODO()).WithFields(log.Fields{"path": path, "type": hdr.Typeflag}).Debug("skipping device nodes in a userns") - return nil - } - // Handle this is an OS-specific way - if err := handleTarTypeBlockCharFifo(hdr, path); err != nil { - return err - } - - case tar.TypeFifo: - // Handle this is an OS-specific way - if err := handleTarTypeBlockCharFifo(hdr, path); err != nil { - if inUserns && errors.Is(err, syscall.EPERM) { - // In most cases, cannot create a fifo if running in user namespace - log.G(context.TODO()).WithFields(log.Fields{"error": err, "path": path, "type": hdr.Typeflag}).Debug("creating fifo node in a userns") - return nil - } - return err - } - - case tar.TypeLink: - // #nosec G305 -- The target path is checked for path traversal. - targetPath := filepath.Join(extractDir, hdr.Linkname) - // check for hardlink breakout - if !strings.HasPrefix(targetPath, extractDir) { - return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname)) - } - if err := os.Link(targetPath, path); err != nil { - return err - } - - case tar.TypeSymlink: - // path -> hdr.Linkname = targetPath - // e.g. /extractDir/path/to/symlink -> ../2/file = /extractDir/path/2/file - targetPath := filepath.Join(filepath.Dir(path), hdr.Linkname) // #nosec G305 -- The target path is checked for path traversal. - - // the reason we don't need to check symlinks in the path (with FollowSymlinkInScope) is because - // that symlink would first have to be created, which would be caught earlier, at this very check: - if !strings.HasPrefix(targetPath, extractDir) { - return breakoutError(fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname)) - } - if err := os.Symlink(hdr.Linkname, path); err != nil { - return err - } - - case tar.TypeXGlobalHeader: - log.G(context.TODO()).Debug("PAX Global Extended Headers found and ignored") - return nil - - default: - return fmt.Errorf("unhandled tar header type %d", hdr.Typeflag) - } - - // Lchown is not supported on Windows. - if Lchown && runtime.GOOS != "windows" { - if chownOpts == nil { - chownOpts = &ChownOpts{UID: hdr.Uid, GID: hdr.Gid} - } - if err := os.Lchown(path, chownOpts.UID, chownOpts.GID); err != nil { - var msg string - if inUserns && errors.Is(err, syscall.EINVAL) { - msg = " (try increasing the number of subordinate IDs in /etc/subuid and /etc/subgid)" - } - return fmt.Errorf("failed to Lchown %q for UID %d, GID %d%s: %w", path, hdr.Uid, hdr.Gid, msg, err) - } - } - - var xattrErrs []string - for key, value := range hdr.PAXRecords { - xattr, ok := strings.CutPrefix(key, paxSchilyXattr) - if !ok { - continue - } - if err := lsetxattr(path, xattr, []byte(value), 0); err != nil { - if bestEffortXattrs && errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.EPERM) { - // EPERM occurs if modifying xattrs is not allowed. This can - // happen when running in userns with restrictions (ChromeOS). - xattrErrs = append(xattrErrs, err.Error()) - continue - } - return err - } - } - - if len(xattrErrs) > 0 { - log.G(context.TODO()).WithFields(log.Fields{ - "errors": xattrErrs, - }).Warn("ignored xattrs in archive: underlying filesystem doesn't support them") - } - - // There is no LChmod, so ignore mode for symlink. Also, this - // must happen after chown, as that can modify the file mode - if err := handleLChmod(hdr, path, hdrInfo); err != nil { - return err - } - - aTime := boundTime(latestTime(hdr.AccessTime, hdr.ModTime)) - mTime := boundTime(hdr.ModTime) - - // chtimes doesn't support a NOFOLLOW flag atm - if hdr.Typeflag == tar.TypeLink { - if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { - if err := chtimes(path, aTime, mTime); err != nil { - return err - } - } - } else if hdr.Typeflag != tar.TypeSymlink { - if err := chtimes(path, aTime, mTime); err != nil { - return err - } - } else { - if err := lchtimes(path, aTime, mTime); err != nil { - return err - } - } - return nil -} - -// Tar creates an archive from the directory at `path`, and returns it as a -// stream of bytes. -func Tar(path string, comp compression.Compression) (io.ReadCloser, error) { - return TarWithOptions(path, &TarOptions{Compression: comp}) -} - -// TarWithOptions creates an archive from the directory at `path`, only including files whose relative -// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. -func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { - tb, err := NewTarballer(srcPath, options) - if err != nil { - return nil, err - } - go tb.Do() - return tb.Reader(), nil -} - -// Tarballer is a lower-level interface to TarWithOptions which gives the caller -// control over which goroutine the archiving operation executes on. -type Tarballer struct { - srcPath string - options *TarOptions - pm *patternmatcher.PatternMatcher - pipeReader *io.PipeReader - pipeWriter *io.PipeWriter - compressWriter io.WriteCloser - whiteoutConverter tarWhiteoutConverter -} - -// NewTarballer constructs a new tarballer. The arguments are the same as for -// TarWithOptions. -func NewTarballer(srcPath string, options *TarOptions) (*Tarballer, error) { - pm, err := patternmatcher.New(options.ExcludePatterns) - if err != nil { - return nil, err - } - - pipeReader, pipeWriter := io.Pipe() - - compressWriter, err := compression.CompressStream(pipeWriter, options.Compression) - if err != nil { - return nil, err - } - - return &Tarballer{ - // Fix the source path to work with long path names. This is a no-op - // on platforms other than Windows. - srcPath: addLongPathPrefix(srcPath), - options: options, - pm: pm, - pipeReader: pipeReader, - pipeWriter: pipeWriter, - compressWriter: compressWriter, - whiteoutConverter: getWhiteoutConverter(options.WhiteoutFormat), - }, nil -} - -// Reader returns the reader for the created archive. -func (t *Tarballer) Reader() io.ReadCloser { - return t.pipeReader -} - -// Do performs the archiving operation in the background. The resulting archive -// can be read from t.Reader(). Do should only be called once on each Tarballer -// instance. -func (t *Tarballer) Do() { - ta := newTarAppender( - t.options.IDMap, - t.compressWriter, - t.options.ChownOpts, - ) - ta.WhiteoutConverter = t.whiteoutConverter - - defer func() { - // Make sure to check the error on Close. - if err := ta.TarWriter.Close(); err != nil { - log.G(context.TODO()).Errorf("Can't close tar writer: %s", err) - } - if err := t.compressWriter.Close(); err != nil { - log.G(context.TODO()).Errorf("Can't close compress writer: %s", err) - } - if err := t.pipeWriter.Close(); err != nil { - log.G(context.TODO()).Errorf("Can't close pipe writer: %s", err) - } - }() - - // In general we log errors here but ignore them because - // during e.g. a diff operation the container can continue - // mutating the filesystem and we can see transient errors - // from this - - stat, err := os.Lstat(t.srcPath) - if err != nil { - return - } - - if !stat.IsDir() { - // We can't later join a non-dir with any includes because the - // 'walk' will error if "file/." is stat-ed and "file" is not a - // directory. So, we must split the source path and use the - // basename as the include. - if len(t.options.IncludeFiles) > 0 { - log.G(context.TODO()).Warn("Tar: Can't archive a file with includes") - } - - dir, base := SplitPathDirEntry(t.srcPath) - t.srcPath = dir - t.options.IncludeFiles = []string{base} - } - - if len(t.options.IncludeFiles) == 0 { - t.options.IncludeFiles = []string{"."} - } - - seen := make(map[string]bool) - - for _, include := range t.options.IncludeFiles { - rebaseName := t.options.RebaseNames[include] - - var ( - parentMatchInfo []patternmatcher.MatchInfo - parentDirs []string - ) - - walkRoot := getWalkRoot(t.srcPath, include) - // TODO(thaJeztah): should this error be handled? - _ = filepath.WalkDir(walkRoot, func(filePath string, f os.DirEntry, err error) error { - if err != nil { - log.G(context.TODO()).Errorf("Tar: Can't stat file %s to tar: %s", t.srcPath, err) - return nil - } - - relFilePath, err := filepath.Rel(t.srcPath, filePath) - if err != nil || (!t.options.IncludeSourceDir && relFilePath == "." && f.IsDir()) { - // Error getting relative path OR we are looking - // at the source directory path. Skip in both situations. - return nil - } - - if t.options.IncludeSourceDir && include == "." && relFilePath != "." { - relFilePath = strings.Join([]string{".", relFilePath}, string(filepath.Separator)) - } - - skip := false - - // If "include" is an exact match for the current file - // then even if there's an "excludePatterns" pattern that - // matches it, don't skip it. IOW, assume an explicit 'include' - // is asking for that file no matter what - which is true - // for some files, like .dockerignore and Dockerfile (sometimes) - if include != relFilePath { - for len(parentDirs) != 0 { - lastParentDir := parentDirs[len(parentDirs)-1] - if strings.HasPrefix(relFilePath, lastParentDir+string(os.PathSeparator)) { - break - } - parentDirs = parentDirs[:len(parentDirs)-1] - parentMatchInfo = parentMatchInfo[:len(parentMatchInfo)-1] - } - - var matchInfo patternmatcher.MatchInfo - if len(parentMatchInfo) != 0 { - skip, matchInfo, err = t.pm.MatchesUsingParentResults(relFilePath, parentMatchInfo[len(parentMatchInfo)-1]) - } else { - skip, matchInfo, err = t.pm.MatchesUsingParentResults(relFilePath, patternmatcher.MatchInfo{}) - } - if err != nil { - log.G(context.TODO()).Errorf("Error matching %s: %v", relFilePath, err) - return err - } - - if f.IsDir() { - parentDirs = append(parentDirs, relFilePath) - parentMatchInfo = append(parentMatchInfo, matchInfo) - } - } - - if skip { - // If we want to skip this file and its a directory - // then we should first check to see if there's an - // excludes pattern (e.g. !dir/file) that starts with this - // dir. If so then we can't skip this dir. - - // Its not a dir then so we can just return/skip. - if !f.IsDir() { - return nil - } - - // No exceptions (!...) in patterns so just skip dir - if !t.pm.Exclusions() { - return filepath.SkipDir - } - - dirSlash := relFilePath + string(filepath.Separator) - - for _, pat := range t.pm.Patterns() { - if !pat.Exclusion() { - continue - } - if strings.HasPrefix(pat.String()+string(filepath.Separator), dirSlash) { - // found a match - so can't skip this dir - return nil - } - } - - // No matching exclusion dir so just skip dir - return filepath.SkipDir - } - - if seen[relFilePath] { - return nil - } - seen[relFilePath] = true - - // Rename the base resource. - if rebaseName != "" { - var replacement string - if rebaseName != string(filepath.Separator) { - // Special case the root directory to replace with an - // empty string instead so that we don't end up with - // double slashes in the paths. - replacement = rebaseName - } - - relFilePath = strings.Replace(relFilePath, include, replacement, 1) - } - - if err := ta.addTarFile(filePath, relFilePath); err != nil { - log.G(context.TODO()).Errorf("Can't add file %s to tar: %s", filePath, err) - // if pipe is broken, stop writing tar stream to it - if errors.Is(err, io.ErrClosedPipe) { - return err - } - } - return nil - }) - } -} - -// Unpack unpacks the decompressedArchive to dest with options. -func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error { - tr := tar.NewReader(decompressedArchive) - - var dirs []*tar.Header - whiteoutConverter := getWhiteoutConverter(options.WhiteoutFormat) - - // Iterate through the files in the archive. -loop: - for { - hdr, err := tr.Next() - if errors.Is(err, io.EOF) { - // end of tar archive - break - } - if err != nil { - return err - } - - // ignore XGlobalHeader early to avoid creating parent directories for them - if hdr.Typeflag == tar.TypeXGlobalHeader { - log.G(context.TODO()).Debugf("PAX Global Extended Headers found for %s and ignored", hdr.Name) - continue - } - - // Normalize name, for safety and for a simple is-root check - // This keeps "../" as-is, but normalizes "/../" to "/". Or Windows: - // This keeps "..\" as-is, but normalizes "\..\" to "\". - hdr.Name = filepath.Clean(hdr.Name) - - for _, exclude := range options.ExcludePatterns { - if strings.HasPrefix(hdr.Name, exclude) { - continue loop - } - } - - // Ensure that the parent directory exists. - err = createImpliedDirectories(dest, hdr, options) - if err != nil { - return err - } - - // #nosec G305 -- The joined path is checked for path traversal. - path := filepath.Join(dest, hdr.Name) - rel, err := filepath.Rel(dest, path) - if err != nil { - return err - } - if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { - return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) - } - - // If path exits we almost always just want to remove and replace it - // The only exception is when it is a directory *and* the file from - // the layer is also a directory. Then we want to merge them (i.e. - // just apply the metadata from the layer). - if fi, err := os.Lstat(path); err == nil { - if options.NoOverwriteDirNonDir && fi.IsDir() && hdr.Typeflag != tar.TypeDir { - // If NoOverwriteDirNonDir is true then we cannot replace - // an existing directory with a non-directory from the archive. - return fmt.Errorf("cannot overwrite directory %q with non-directory %q", path, dest) - } - - if options.NoOverwriteDirNonDir && !fi.IsDir() && hdr.Typeflag == tar.TypeDir { - // If NoOverwriteDirNonDir is true then we cannot replace - // an existing non-directory with a directory from the archive. - return fmt.Errorf("cannot overwrite non-directory %q with directory %q", path, dest) - } - - if fi.IsDir() && hdr.Name == "." { - continue - } - - if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { - if err := os.RemoveAll(path); err != nil { - return err - } - } - } - - if err := remapIDs(options.IDMap, hdr); err != nil { - return err - } - - if whiteoutConverter != nil { - writeFile, err := whiteoutConverter.ConvertRead(hdr, path) - if err != nil { - return err - } - if !writeFile { - continue - } - } - - if err := createTarFile(path, dest, hdr, tr, options); err != nil { - return err - } - - // Directory mtimes must be handled at the end to avoid further - // file creation in them to modify the directory mtime - if hdr.Typeflag == tar.TypeDir { - dirs = append(dirs, hdr) - } - } - - for _, hdr := range dirs { - // #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice. - path := filepath.Join(dest, hdr.Name) - - if err := chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil { - return err - } - } - return nil -} - -// createImpliedDirectories will create all parent directories of the current path with default permissions, if they do -// not already exist. This is possible as the tar format supports 'implicit' directories, where their existence is -// defined by the paths of files in the tar, but there are no header entries for the directories themselves, and thus -// we most both create them and choose metadata like permissions. -// -// The caller should have performed filepath.Clean(hdr.Name), so hdr.Name will now be in the filepath format for the OS -// on which the daemon is running. This precondition is required because this function assumes a OS-specific path -// separator when checking that a path is not the root. -func createImpliedDirectories(dest string, hdr *tar.Header, options *TarOptions) error { - // Not the root directory, ensure that the parent directory exists - if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) { - parent := filepath.Dir(hdr.Name) - parentPath := filepath.Join(dest, parent) - if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { - // RootPair() is confined inside this loop as most cases will not require a call, so we can spend some - // unneeded function calls in the uncommon case to encapsulate logic -- implied directories are a niche - // usage that reduces the portability of an image. - uid, gid := options.IDMap.RootPair() - - err = user.MkdirAllAndChown(parentPath, ImpliedDirectoryMode, uid, gid, user.WithOnlyNew) - if err != nil { - return err - } - } - } - - return nil -} - -// Untar reads a stream of bytes from `archive`, parses it as a tar archive, -// and unpacks it into the directory at `dest`. -// The archive may be compressed with one of the following algorithms: -// identity (uncompressed), gzip, bzip2, xz. -// -// FIXME: specify behavior when target path exists vs. doesn't exist. -func Untar(tarArchive io.Reader, dest string, options *TarOptions) error { - return untarHandler(tarArchive, dest, options, true) -} - -// UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive, -// and unpacks it into the directory at `dest`. -// The archive must be an uncompressed stream. -func UntarUncompressed(tarArchive io.Reader, dest string, options *TarOptions) error { - return untarHandler(tarArchive, dest, options, false) -} - -// Handler for teasing out the automatic decompression -func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decompress bool) error { - if tarArchive == nil { - return errors.New("empty archive") - } - dest = filepath.Clean(dest) - if options == nil { - options = &TarOptions{} - } - if options.ExcludePatterns == nil { - options.ExcludePatterns = []string{} - } - - r := tarArchive - if decompress { - decompressedArchive, err := compression.DecompressStream(tarArchive) - if err != nil { - return err - } - defer decompressedArchive.Close() - r = decompressedArchive - } - - return Unpack(r, dest, options) -} - -// TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other. -// If either Tar or Untar fails, TarUntar aborts and returns the error. -func (archiver *Archiver) TarUntar(src, dst string) error { - archive, err := Tar(src, compression.None) - if err != nil { - return err - } - defer archive.Close() - return archiver.Untar(archive, dst, &TarOptions{ - IDMap: archiver.IDMapping, - }) -} - -// UntarPath untar a file from path to a destination, src is the source tar file path. -func (archiver *Archiver) UntarPath(src, dst string) error { - archive, err := os.Open(src) - if err != nil { - return err - } - defer archive.Close() - return archiver.Untar(archive, dst, &TarOptions{ - IDMap: archiver.IDMapping, - }) -} - -// CopyWithTar creates a tar archive of filesystem path `src`, and -// unpacks it at filesystem path `dst`. -// The archive is streamed directly with fixed buffering and no -// intermediary disk IO. -func (archiver *Archiver) CopyWithTar(src, dst string) error { - srcSt, err := os.Stat(src) - if err != nil { - return err - } - if !srcSt.IsDir() { - return archiver.CopyFileWithTar(src, dst) - } - - // if this Archiver is set up with ID mapping we need to create - // the new destination directory with the remapped root UID/GID pair - // as owner - uid, gid := archiver.IDMapping.RootPair() - // Create dst, copy src's content into it - if err := user.MkdirAllAndChown(dst, 0o755, uid, gid, user.WithOnlyNew); err != nil { - return err - } - return archiver.TarUntar(src, dst) -} - -// CopyFileWithTar emulates the behavior of the 'cp' command-line -// for a single file. It copies a regular file from path `src` to -// path `dst`, and preserves all its metadata. -func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { - srcSt, err := os.Stat(src) - if err != nil { - return err - } - - if srcSt.IsDir() { - return errors.New("can't copy a directory") - } - - // Clean up the trailing slash. This must be done in an operating - // system specific manner. - if dst[len(dst)-1] == os.PathSeparator { - dst = filepath.Join(dst, filepath.Base(src)) - } - // Create the holding directory if necessary - if err := os.MkdirAll(filepath.Dir(dst), 0o700); err != nil { - return err - } - - r, w := io.Pipe() - errC := make(chan error, 1) - - go func() { - defer close(errC) - - errC <- func() error { - defer w.Close() - - srcF, err := os.Open(src) - if err != nil { - return err - } - defer srcF.Close() - - hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "") - if err != nil { - return err - } - hdr.Format = tar.FormatPAX - hdr.ModTime = hdr.ModTime.Truncate(time.Second) - hdr.AccessTime = time.Time{} - hdr.ChangeTime = time.Time{} - hdr.Name = filepath.Base(dst) - hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) - - if err := remapIDs(archiver.IDMapping, hdr); err != nil { - return err - } - - tw := tar.NewWriter(w) - defer tw.Close() - if err := tw.WriteHeader(hdr); err != nil { - return err - } - if err := copyWithBuffer(tw, srcF); err != nil { - return err - } - return nil - }() - }() - defer func() { - if er := <-errC; err == nil && er != nil { - err = er - } - }() - - err = archiver.Untar(r, filepath.Dir(dst), nil) - if err != nil { - r.CloseWithError(err) - } - return err -} - -// IdentityMapping returns the IdentityMapping of the archiver. -func (archiver *Archiver) IdentityMapping() user.IdentityMapping { - return archiver.IDMapping -} - -func remapIDs(idMapping user.IdentityMapping, hdr *tar.Header) error { - uid, gid, err := idMapping.ToHost(hdr.Uid, hdr.Gid) - hdr.Uid, hdr.Gid = uid, gid - return err -} diff --git a/vendor/github.com/moby/go-archive/archive_linux.go b/vendor/github.com/moby/go-archive/archive_linux.go deleted file mode 100644 index 7b6c3e02b643..000000000000 --- a/vendor/github.com/moby/go-archive/archive_linux.go +++ /dev/null @@ -1,107 +0,0 @@ -package archive - -import ( - "archive/tar" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/moby/sys/userns" - "golang.org/x/sys/unix" -) - -func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { - if format == OverlayWhiteoutFormat { - return overlayWhiteoutConverter{} - } - return nil -} - -type overlayWhiteoutConverter struct{} - -func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, _ error) { - // convert whiteouts to AUFS format - if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 { - // we just rename the file and make it normal - dir, filename := filepath.Split(hdr.Name) - hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename) - hdr.Mode = 0o600 - hdr.Typeflag = tar.TypeReg - hdr.Size = 0 - } - - if fi.Mode()&os.ModeDir == 0 { - // FIXME(thaJeztah): return a sentinel error instead of nil, nil - return nil, nil - } - - opaqueXattrName := "trusted.overlay.opaque" - if userns.RunningInUserNS() { - opaqueXattrName = "user.overlay.opaque" - } - - // convert opaque dirs to AUFS format by writing an empty file with the prefix - opaque, err := lgetxattr(path, opaqueXattrName) - if err != nil { - return nil, err - } - if len(opaque) != 1 || opaque[0] != 'y' { - // FIXME(thaJeztah): return a sentinel error instead of nil, nil - return nil, nil - } - delete(hdr.PAXRecords, paxSchilyXattr+opaqueXattrName) - - // create a header for the whiteout file - // it should inherit some properties from the parent, but be a regular file - return &tar.Header{ - Typeflag: tar.TypeReg, - Mode: hdr.Mode & int64(os.ModePerm), - Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted. - Size: 0, - Uid: hdr.Uid, - Uname: hdr.Uname, - Gid: hdr.Gid, - Gname: hdr.Gname, - AccessTime: hdr.AccessTime, - ChangeTime: hdr.ChangeTime, - }, nil -} - -func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) { - base := filepath.Base(path) - dir := filepath.Dir(path) - - // if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay - if base == WhiteoutOpaqueDir { - opaqueXattrName := "trusted.overlay.opaque" - if userns.RunningInUserNS() { - opaqueXattrName = "user.overlay.opaque" - } - - err := unix.Setxattr(dir, opaqueXattrName, []byte{'y'}, 0) - if err != nil { - return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, opaqueXattrName, err) - } - // don't write the file itself - return false, err - } - - // 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, fmt.Errorf("failed to mknod('%s', S_IFCHR, 0): %w", originalPath, err) - } - if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil { - return false, err - } - - // don't write the file itself - return false, nil - } - - return true, nil -} diff --git a/vendor/github.com/moby/go-archive/archive_other.go b/vendor/github.com/moby/go-archive/archive_other.go deleted file mode 100644 index 6495549f60e8..000000000000 --- a/vendor/github.com/moby/go-archive/archive_other.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !linux - -package archive - -func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { - return nil -} diff --git a/vendor/github.com/moby/go-archive/archive_unix.go b/vendor/github.com/moby/go-archive/archive_unix.go deleted file mode 100644 index 3a9f5b0b5863..000000000000 --- a/vendor/github.com/moby/go-archive/archive_unix.go +++ /dev/null @@ -1,86 +0,0 @@ -//go:build !windows - -package archive - -import ( - "archive/tar" - "errors" - "os" - "path/filepath" - "strings" - "syscall" - - "golang.org/x/sys/unix" -) - -// addLongPathPrefix adds the Windows long path prefix to the path provided if -// it does not already have it. It is a no-op on platforms other than Windows. -func addLongPathPrefix(srcPath string) string { - return srcPath -} - -// getWalkRoot calculates the root path when performing a TarWithOptions. -// We use a separate function as this is platform specific. On Linux, we -// can't use filepath.Join(srcPath,include) because this will clean away -// a trailing "." or "/" which may be important. -func getWalkRoot(srcPath string, include string) string { - return strings.TrimSuffix(srcPath, string(filepath.Separator)) + string(filepath.Separator) + include -} - -// chmodTarEntry is used to adjust the file permissions used in tar header based -// on the platform the archival is done. -func chmodTarEntry(perm os.FileMode) os.FileMode { - return perm // noop for unix as golang APIs provide perm bits correctly -} - -func getInodeFromStat(stat interface{}) (uint64, error) { - s, ok := stat.(*syscall.Stat_t) - if !ok { - // FIXME(thaJeztah): this should likely return an error; see https://github.com/moby/moby/pull/49493#discussion_r1979152897 - return 0, nil - } - return s.Ino, nil -} - -func getFileUIDGID(stat interface{}) (int, int, error) { - s, ok := stat.(*syscall.Stat_t) - - if !ok { - return 0, 0, errors.New("cannot convert stat value to syscall.Stat_t") - } - return int(s.Uid), int(s.Gid), nil -} - -// handleTarTypeBlockCharFifo is an OS-specific helper function used by -// createTarFile to handle the following types of header: Block; Char; Fifo. -// -// Creating device nodes is not supported when running in a user namespace, -// produces a [syscall.EPERM] in most cases. -func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { - mode := uint32(hdr.Mode & 0o7777) - switch hdr.Typeflag { - case tar.TypeBlock: - mode |= unix.S_IFBLK - case tar.TypeChar: - mode |= unix.S_IFCHR - case tar.TypeFifo: - mode |= unix.S_IFIFO - } - - return mknod(path, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor))) -} - -func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { - if hdr.Typeflag == tar.TypeLink { - if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { - if err := os.Chmod(path, hdrInfo.Mode()); err != nil { - return err - } - } - } else if hdr.Typeflag != tar.TypeSymlink { - if err := os.Chmod(path, hdrInfo.Mode()); err != nil { - return err - } - } - return nil -} diff --git a/vendor/github.com/moby/go-archive/archive_windows.go b/vendor/github.com/moby/go-archive/archive_windows.go deleted file mode 100644 index 0e3e316afc2f..000000000000 --- a/vendor/github.com/moby/go-archive/archive_windows.go +++ /dev/null @@ -1,62 +0,0 @@ -package archive - -import ( - "archive/tar" - "os" - "path/filepath" - "strings" -) - -// longPathPrefix is the longpath prefix for Windows file paths. -const longPathPrefix = `\\?\` - -// addLongPathPrefix adds the Windows long path prefix to the path provided if -// it does not already have it. It is a no-op on platforms other than Windows. -// -// addLongPathPrefix is a copy of [github.com/docker/docker/pkg/longpath.AddPrefix]. -func addLongPathPrefix(srcPath string) string { - if strings.HasPrefix(srcPath, longPathPrefix) { - return srcPath - } - if strings.HasPrefix(srcPath, `\\`) { - // This is a UNC path, so we need to add 'UNC' to the path as well. - return longPathPrefix + `UNC` + srcPath[1:] - } - return longPathPrefix + srcPath -} - -// getWalkRoot calculates the root path when performing a TarWithOptions. -// We use a separate function as this is platform specific. -func getWalkRoot(srcPath string, include string) string { - return filepath.Join(srcPath, include) -} - -// chmodTarEntry is used to adjust the file permissions used in tar header based -// on the platform the archival is done. -func chmodTarEntry(perm os.FileMode) os.FileMode { - // Remove group- and world-writable bits. - perm &= 0o755 - - // Add the x bit: make everything +x on Windows - return perm | 0o111 -} - -func getInodeFromStat(stat interface{}) (uint64, error) { - // do nothing. no notion of Inode in stat on Windows - return 0, nil -} - -// handleTarTypeBlockCharFifo is an OS-specific helper function used by -// createTarFile to handle the following types of header: Block; Char; Fifo -func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { - return nil -} - -func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { - return nil -} - -func getFileUIDGID(stat interface{}) (int, int, error) { - // no notion of file ownership mapping yet on Windows - return 0, 0, nil -} diff --git a/vendor/github.com/moby/go-archive/changes.go b/vendor/github.com/moby/go-archive/changes.go deleted file mode 100644 index 02a0372c6f99..000000000000 --- a/vendor/github.com/moby/go-archive/changes.go +++ /dev/null @@ -1,430 +0,0 @@ -package archive - -import ( - "archive/tar" - "bytes" - "context" - "fmt" - "io" - "io/fs" - "os" - "path/filepath" - "sort" - "strings" - "time" - - "github.com/containerd/log" - "github.com/moby/sys/user" -) - -// ChangeType represents the change type. -type ChangeType int - -const ( - ChangeModify = 0 // ChangeModify represents the modify operation. - ChangeAdd = 1 // ChangeAdd represents the add operation. - ChangeDelete = 2 // ChangeDelete represents the delete operation. -) - -func (c ChangeType) String() string { - switch c { - case ChangeModify: - return "C" - case ChangeAdd: - return "A" - case ChangeDelete: - return "D" - } - return "" -} - -// Change represents a change, it wraps the change type and path. -// It describes changes of the files in the path respect to the -// parent layers. The change could be modify, add, delete. -// This is used for layer diff. -type Change struct { - Path string - Kind ChangeType -} - -func (change *Change) String() string { - return fmt.Sprintf("%s %s", change.Kind, change.Path) -} - -// for sort.Sort -type changesByPath []Change - -func (c changesByPath) Less(i, j int) bool { return c[i].Path < c[j].Path } -func (c changesByPath) Len() int { return len(c) } -func (c changesByPath) Swap(i, j int) { c[j], c[i] = c[i], c[j] } - -// Gnu tar doesn't have sub-second mtime precision. The go tar -// writer (1.10+) does when using PAX format, but we round times to seconds -// to ensure archives have the same hashes for backwards compatibility. -// See https://github.com/moby/moby/pull/35739/commits/fb170206ba12752214630b269a40ac7be6115ed4. -// -// Non-sub-second is problematic when we apply changes via tar -// files. We handle this by comparing for exact times, *or* same -// second count and either a or b having exactly 0 nanoseconds -func sameFsTime(a, b time.Time) bool { - return a.Equal(b) || - (a.Unix() == b.Unix() && - (a.Nanosecond() == 0 || b.Nanosecond() == 0)) -} - -// Changes walks the path rw and determines changes for the files in the path, -// with respect to the parent layers -func Changes(layers []string, rw string) ([]Change, error) { - return collectChanges(layers, rw, aufsDeletedFile, aufsMetadataSkip) -} - -func aufsMetadataSkip(path string) (skip bool, err error) { - skip, err = filepath.Match(string(os.PathSeparator)+WhiteoutMetaPrefix+"*", path) - if err != nil { - skip = true - } - return skip, err -} - -func aufsDeletedFile(root, path string, fi os.FileInfo) (string, error) { - f := filepath.Base(path) - - // If there is a whiteout, then the file was removed - if strings.HasPrefix(f, WhiteoutPrefix) { - originalFile := f[len(WhiteoutPrefix):] - return filepath.Join(filepath.Dir(path), originalFile), nil - } - - return "", nil -} - -type ( - skipChange func(string) (bool, error) - deleteChange func(string, string, os.FileInfo) (string, error) -) - -func collectChanges(layers []string, rw string, dc deleteChange, sc skipChange) ([]Change, error) { - var ( - changes []Change - changedDirs = make(map[string]struct{}) - ) - - err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - - // Rebase path - path, err = filepath.Rel(rw, path) - if err != nil { - return err - } - - // As this runs on the daemon side, file paths are OS specific. - path = filepath.Join(string(os.PathSeparator), path) - - // Skip root - if path == string(os.PathSeparator) { - return nil - } - - if sc != nil { - if skip, err := sc(path); skip { - return err - } - } - - change := Change{ - Path: path, - } - - deletedFile, err := dc(rw, path, f) - if err != nil { - return err - } - - // Find out what kind of modification happened - if deletedFile != "" { - change.Path = deletedFile - change.Kind = ChangeDelete - } else { - // Otherwise, the file was added - change.Kind = ChangeAdd - - // ...Unless it already existed in a top layer, in which case, it's a modification - for _, layer := range layers { - stat, err := os.Stat(filepath.Join(layer, path)) - if err != nil && !os.IsNotExist(err) { - return err - } - if err == nil { - // The file existed in the top layer, so that's a modification - - // However, if it's a directory, maybe it wasn't actually modified. - // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar - if stat.IsDir() && f.IsDir() { - if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) { - // Both directories are the same, don't record the change - return nil - } - } - change.Kind = ChangeModify - break - } - } - } - - // If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files. - // This block is here to ensure the change is recorded even if the - // modify time, mode and size of the parent directory in the rw and ro layers are all equal. - // Check https://github.com/docker/docker/pull/13590 for details. - if f.IsDir() { - changedDirs[path] = struct{}{} - } - if change.Kind == ChangeAdd || change.Kind == ChangeDelete { - parent := filepath.Dir(path) - if _, ok := changedDirs[parent]; !ok && parent != "/" { - changes = append(changes, Change{Path: parent, Kind: ChangeModify}) - changedDirs[parent] = struct{}{} - } - } - - // Record change - changes = append(changes, change) - return nil - }) - if err != nil && !os.IsNotExist(err) { - return nil, err - } - return changes, nil -} - -// FileInfo describes the information of a file. -type FileInfo struct { - parent *FileInfo - name string - stat fs.FileInfo - children map[string]*FileInfo - capability []byte - added bool -} - -// LookUp looks up the file information of a file. -func (info *FileInfo) LookUp(path string) *FileInfo { - // As this runs on the daemon side, file paths are OS specific. - parent := info - if path == string(os.PathSeparator) { - return info - } - - pathElements := strings.Split(path, string(os.PathSeparator)) - for _, elem := range pathElements { - if elem != "" { - child := parent.children[elem] - if child == nil { - return nil - } - parent = child - } - } - return parent -} - -func (info *FileInfo) path() string { - if info.parent == nil { - // As this runs on the daemon side, file paths are OS specific. - return string(os.PathSeparator) - } - return filepath.Join(info.parent.path(), info.name) -} - -func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { - sizeAtEntry := len(*changes) - - if oldInfo == nil { - // add - change := Change{ - Path: info.path(), - Kind: ChangeAdd, - } - *changes = append(*changes, change) - info.added = true - } - - // We make a copy so we can modify it to detect additions - // also, we only recurse on the old dir if the new info is a directory - // otherwise any previous delete/change is considered recursive - oldChildren := make(map[string]*FileInfo) - if oldInfo != nil && info.isDir() { - for k, v := range oldInfo.children { - oldChildren[k] = v - } - } - - for name, newChild := range info.children { - oldChild := oldChildren[name] - if oldChild != nil { - // change? - oldStat := oldChild.stat - newStat := newChild.stat - // Note: We can't compare inode or ctime or blocksize here, because these change - // when copying a file into a container. However, that is not generally a problem - // because any content change will change mtime, and any status change should - // be visible when actually comparing the stat fields. The only time this - // breaks down is if some code intentionally hides a change by setting - // back mtime - if statDifferent(oldStat, newStat) || - !bytes.Equal(oldChild.capability, newChild.capability) { - change := Change{ - Path: newChild.path(), - Kind: ChangeModify, - } - *changes = append(*changes, change) - newChild.added = true - } - - // Remove from copy so we can detect deletions - delete(oldChildren, name) - } - - newChild.addChanges(oldChild, changes) - } - for _, oldChild := range oldChildren { - // delete - change := Change{ - Path: oldChild.path(), - Kind: ChangeDelete, - } - *changes = append(*changes, change) - } - - // If there were changes inside this directory, we need to add it, even if the directory - // itself wasn't changed. This is needed to properly save and restore filesystem permissions. - // As this runs on the daemon side, file paths are OS specific. - if len(*changes) > sizeAtEntry && info.isDir() && !info.added && info.path() != string(os.PathSeparator) { - change := Change{ - Path: info.path(), - Kind: ChangeModify, - } - // Let's insert the directory entry before the recently added entries located inside this dir - *changes = append(*changes, change) // just to resize the slice, will be overwritten - copy((*changes)[sizeAtEntry+1:], (*changes)[sizeAtEntry:]) - (*changes)[sizeAtEntry] = change - } -} - -// Changes add changes to file information. -func (info *FileInfo) Changes(oldInfo *FileInfo) []Change { - var changes []Change - - info.addChanges(oldInfo, &changes) - - return changes -} - -func newRootFileInfo() *FileInfo { - // As this runs on the daemon side, file paths are OS specific. - root := &FileInfo{ - name: string(os.PathSeparator), - children: make(map[string]*FileInfo), - } - return root -} - -// ChangesDirs compares two directories and generates an array of Change objects describing the changes. -// If oldDir is "", then all files in newDir will be Add-Changes. -func ChangesDirs(newDir, oldDir string) ([]Change, error) { - var oldRoot, newRoot *FileInfo - if oldDir == "" { - emptyDir, err := os.MkdirTemp("", "empty") - if err != nil { - return nil, err - } - defer os.Remove(emptyDir) - oldDir = emptyDir - } - oldRoot, newRoot, err := collectFileInfoForChanges(oldDir, newDir) - if err != nil { - return nil, err - } - - return newRoot.Changes(oldRoot), nil -} - -// ChangesSize calculates the size in bytes of the provided changes, based on newDir. -func ChangesSize(newDir string, changes []Change) int64 { - var ( - size int64 - sf = make(map[uint64]struct{}) - ) - for _, change := range changes { - if change.Kind == ChangeModify || change.Kind == ChangeAdd { - file := filepath.Join(newDir, change.Path) - fileInfo, err := os.Lstat(file) - if err != nil { - log.G(context.TODO()).Errorf("Can not stat %q: %s", file, err) - continue - } - - if fileInfo != nil && !fileInfo.IsDir() { - if hasHardlinks(fileInfo) { - inode := getIno(fileInfo) - if _, ok := sf[inode]; !ok { - size += fileInfo.Size() - sf[inode] = struct{}{} - } - } else { - size += fileInfo.Size() - } - } - } - } - return size -} - -// ExportChanges produces an Archive from the provided changes, relative to dir. -func ExportChanges(dir string, changes []Change, idMap user.IdentityMapping) (io.ReadCloser, error) { - reader, writer := io.Pipe() - go func() { - ta := newTarAppender(idMap, writer, nil) - - sort.Sort(changesByPath(changes)) - - // In general we log errors here but ignore them because - // during e.g. a diff operation the container can continue - // mutating the filesystem and we can see transient errors - // from this - for _, change := range changes { - if change.Kind == ChangeDelete { - whiteOutDir := filepath.Dir(change.Path) - whiteOutBase := filepath.Base(change.Path) - whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase) - timestamp := time.Now() - hdr := &tar.Header{ - Name: whiteOut[1:], - Size: 0, - ModTime: timestamp, - AccessTime: timestamp, - ChangeTime: timestamp, - } - if err := ta.TarWriter.WriteHeader(hdr); err != nil { - log.G(context.TODO()).Debugf("Can't write whiteout header: %s", err) - } - } else { - path := filepath.Join(dir, change.Path) - if err := ta.addTarFile(path, change.Path[1:]); err != nil { - log.G(context.TODO()).Debugf("Can't add file %s to tar: %s", path, err) - } - } - } - - // Make sure to check the error on Close. - if err := ta.TarWriter.Close(); err != nil { - log.G(context.TODO()).Debugf("Can't close layer: %s", err) - } - if err := writer.Close(); err != nil { - log.G(context.TODO()).Debugf("failed close Changes writer: %s", err) - } - }() - return reader, nil -} diff --git a/vendor/github.com/moby/go-archive/changes_linux.go b/vendor/github.com/moby/go-archive/changes_linux.go deleted file mode 100644 index 8289fe17d906..000000000000 --- a/vendor/github.com/moby/go-archive/changes_linux.go +++ /dev/null @@ -1,274 +0,0 @@ -package archive - -import ( - "fmt" - "os" - "path/filepath" - "sort" - "strings" - "syscall" - "unsafe" - - "golang.org/x/sys/unix" -) - -// walker is used to implement collectFileInfoForChanges on linux. Where this -// method in general returns the entire contents of two directory trees, we -// optimize some FS calls out on linux. In particular, we take advantage of the -// fact that getdents(2) returns the inode of each file in the directory being -// walked, which, when walking two trees in parallel to generate a list of -// changes, can be used to prune subtrees without ever having to lstat(2) them -// directly. Eliminating stat calls in this way can save up to seconds on large -// images. -type walker struct { - dir1 string - dir2 string - root1 *FileInfo - root2 *FileInfo -} - -// collectFileInfoForChanges returns a complete representation of the trees -// rooted at dir1 and dir2, with one important exception: any subtree or -// leaf where the inode and device numbers are an exact match between dir1 -// and dir2 will be pruned from the results. This method is *only* to be used -// to generating a list of changes between the two directories, as it does not -// reflect the full contents. -func collectFileInfoForChanges(dir1, dir2 string) (*FileInfo, *FileInfo, error) { - w := &walker{ - dir1: dir1, - dir2: dir2, - root1: newRootFileInfo(), - root2: newRootFileInfo(), - } - - i1, err := os.Lstat(w.dir1) - if err != nil { - return nil, nil, err - } - i2, err := os.Lstat(w.dir2) - if err != nil { - return nil, nil, err - } - - if err := w.walk("/", i1, i2); err != nil { - return nil, nil, err - } - - return w.root1, w.root2, nil -} - -// Given a FileInfo, its path info, and a reference to the root of the tree -// being constructed, register this file with the tree. -func walkchunk(path string, fi os.FileInfo, dir string, root *FileInfo) error { - if fi == nil { - return nil - } - parent := root.LookUp(filepath.Dir(path)) - if parent == nil { - return fmt.Errorf("walkchunk: Unexpectedly no parent for %s", path) - } - info := &FileInfo{ - name: filepath.Base(path), - children: make(map[string]*FileInfo), - parent: parent, - } - cpath := filepath.Join(dir, path) - info.stat = fi - info.capability, _ = lgetxattr(cpath, "security.capability") // lgetxattr(2): fs access - parent.children[info.name] = info - return nil -} - -// Walk a subtree rooted at the same path in both trees being iterated. For -// example, /docker/overlay/1234/a/b/c/d and /docker/overlay/8888/a/b/c/d -func (w *walker) walk(path string, i1, i2 os.FileInfo) (err error) { - // Register these nodes with the return trees, unless we're still at the - // (already-created) roots: - if path != "/" { - if err := walkchunk(path, i1, w.dir1, w.root1); err != nil { - return err - } - if err := walkchunk(path, i2, w.dir2, w.root2); err != nil { - return err - } - } - - is1Dir := i1 != nil && i1.IsDir() - is2Dir := i2 != nil && i2.IsDir() - - sameDevice := false - if i1 != nil && i2 != nil { - si1 := i1.Sys().(*syscall.Stat_t) - si2 := i2.Sys().(*syscall.Stat_t) - if si1.Dev == si2.Dev { - sameDevice = true - } - } - - // If these files are both non-existent, or leaves (non-dirs), we are done. - if !is1Dir && !is2Dir { - return nil - } - - // Fetch the names of all the files contained in both directories being walked: - var names1, names2 []nameIno - if is1Dir { - names1, err = readdirnames(filepath.Join(w.dir1, path)) // getdents(2): fs access - if err != nil { - return err - } - } - if is2Dir { - names2, err = readdirnames(filepath.Join(w.dir2, path)) // getdents(2): fs access - if err != nil { - return err - } - } - - // We have lists of the files contained in both parallel directories, sorted - // in the same order. Walk them in parallel, generating a unique merged list - // of all items present in either or both directories. - var names []string - ix1 := 0 - ix2 := 0 - - for ix1 < len(names1) && ix2 < len(names2) { - ni1 := names1[ix1] - ni2 := names2[ix2] - - switch strings.Compare(ni1.name, ni2.name) { - case -1: // ni1 < ni2 -- advance ni1 - // we will not encounter ni1 in names2 - names = append(names, ni1.name) - ix1++ - case 0: // ni1 == ni2 - if ni1.ino != ni2.ino || !sameDevice { - names = append(names, ni1.name) - } - ix1++ - ix2++ - case 1: // ni1 > ni2 -- advance ni2 - // we will not encounter ni2 in names1 - names = append(names, ni2.name) - ix2++ - } - } - for ix1 < len(names1) { - names = append(names, names1[ix1].name) - ix1++ - } - for ix2 < len(names2) { - names = append(names, names2[ix2].name) - ix2++ - } - - // For each of the names present in either or both of the directories being - // iterated, stat the name under each root, and recurse the pair of them: - for _, name := range names { - fname := filepath.Join(path, name) - var cInfo1, cInfo2 os.FileInfo - if is1Dir { - cInfo1, err = os.Lstat(filepath.Join(w.dir1, fname)) // lstat(2): fs access - if err != nil && !os.IsNotExist(err) { - return err - } - } - if is2Dir { - cInfo2, err = os.Lstat(filepath.Join(w.dir2, fname)) // lstat(2): fs access - if err != nil && !os.IsNotExist(err) { - return err - } - } - if err = w.walk(fname, cInfo1, cInfo2); err != nil { - return err - } - } - return nil -} - -// {name,inode} pairs used to support the early-pruning logic of the walker type -type nameIno struct { - name string - ino uint64 -} - -type nameInoSlice []nameIno - -func (s nameInoSlice) Len() int { return len(s) } -func (s nameInoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s nameInoSlice) Less(i, j int) bool { return s[i].name < s[j].name } - -// readdirnames is a hacked-apart version of the Go stdlib code, exposing inode -// numbers further up the stack when reading directory contents. Unlike -// os.Readdirnames, which returns a list of filenames, this function returns a -// list of {filename,inode} pairs. -func readdirnames(dirname string) (names []nameIno, err error) { - var ( - size = 100 - buf = make([]byte, 4096) - nbuf int - bufp int - nb int - ) - - f, err := os.Open(dirname) - if err != nil { - return nil, err - } - defer f.Close() - - names = make([]nameIno, 0, size) // Empty with room to grow. - for { - // Refill the buffer if necessary - if bufp >= nbuf { - bufp = 0 - nbuf, err = unix.ReadDirent(int(f.Fd()), buf) // getdents on linux - if nbuf < 0 { - nbuf = 0 - } - if err != nil { - return nil, os.NewSyscallError("readdirent", err) - } - if nbuf <= 0 { - break // EOF - } - } - - // Drain the buffer - nb, names = parseDirent(buf[bufp:nbuf], names) - bufp += nb - } - - sl := nameInoSlice(names) - sort.Sort(sl) - return sl, nil -} - -// parseDirent is a minor modification of unix.ParseDirent (linux version) -// which returns {name,inode} pairs instead of just names. -func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno) { - origlen := len(buf) - for len(buf) > 0 { - dirent := (*unix.Dirent)(unsafe.Pointer(&buf[0])) // #nosec G103 -- Ignore "G103: Use of unsafe calls should be audited" - buf = buf[dirent.Reclen:] - if dirent.Ino == 0 { // File absent in directory. - continue - } - b := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0])) // #nosec G103 -- Ignore "G103: Use of unsafe calls should be audited" - name := string(b[0:clen(b[:])]) - if name == "." || name == ".." { // Useless names - continue - } - names = append(names, nameIno{name, dirent.Ino}) - } - return origlen - len(buf), names -} - -func clen(n []byte) int { - for i := 0; i < len(n); i++ { - if n[i] == 0 { - return i - } - } - return len(n) -} diff --git a/vendor/github.com/moby/go-archive/changes_other.go b/vendor/github.com/moby/go-archive/changes_other.go deleted file mode 100644 index a8a3a5a6faa8..000000000000 --- a/vendor/github.com/moby/go-archive/changes_other.go +++ /dev/null @@ -1,95 +0,0 @@ -//go:build !linux - -package archive - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "strings" -) - -func collectFileInfoForChanges(oldDir, newDir string) (*FileInfo, *FileInfo, error) { - var ( - oldRoot, newRoot *FileInfo - err1, err2 error - errs = make(chan error, 2) - ) - go func() { - oldRoot, err1 = collectFileInfo(oldDir) - errs <- err1 - }() - go func() { - newRoot, err2 = collectFileInfo(newDir) - errs <- err2 - }() - - // block until both routines have returned - for i := 0; i < 2; i++ { - if err := <-errs; err != nil { - return nil, nil, err - } - } - - return oldRoot, newRoot, nil -} - -func collectFileInfo(sourceDir string) (*FileInfo, error) { - root := newRootFileInfo() - - err := filepath.WalkDir(sourceDir, func(path string, _ os.DirEntry, err error) error { - if err != nil { - return err - } - - // Rebase path - relPath, err := filepath.Rel(sourceDir, path) - if err != nil { - return err - } - - // As this runs on the daemon side, file paths are OS specific. - relPath = filepath.Join(string(os.PathSeparator), relPath) - - // See https://github.com/golang/go/issues/9168 - bug in filepath.Join. - // Temporary workaround. If the returned path starts with two backslashes, - // trim it down to a single backslash. Only relevant on Windows. - if runtime.GOOS == "windows" { - if strings.HasPrefix(relPath, `\\`) { - relPath = relPath[1:] - } - } - - if relPath == string(os.PathSeparator) { - return nil - } - - parent := root.LookUp(filepath.Dir(relPath)) - if parent == nil { - return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) - } - - s, err := os.Lstat(path) - if err != nil { - return err - } - - info := &FileInfo{ - name: filepath.Base(relPath), - children: make(map[string]*FileInfo), - parent: parent, - stat: s, - } - - info.capability, _ = lgetxattr(path, "security.capability") - - parent.children[info.name] = info - - return nil - }) - if err != nil { - return nil, err - } - return root, nil -} diff --git a/vendor/github.com/moby/go-archive/changes_unix.go b/vendor/github.com/moby/go-archive/changes_unix.go deleted file mode 100644 index 4dd98bd2935f..000000000000 --- a/vendor/github.com/moby/go-archive/changes_unix.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build !windows - -package archive - -import ( - "io/fs" - "os" - "syscall" -) - -func statDifferent(oldStat fs.FileInfo, newStat fs.FileInfo) bool { - oldSys := oldStat.Sys().(*syscall.Stat_t) - newSys := newStat.Sys().(*syscall.Stat_t) - // Don't look at size for dirs, its not a good measure of change - if oldStat.Mode() != newStat.Mode() || - oldSys.Uid != newSys.Uid || - oldSys.Gid != newSys.Gid || - oldSys.Rdev != newSys.Rdev || - // Don't look at size or modification time for dirs, its not a good - // measure of change. See https://github.com/moby/moby/issues/9874 - // for a description of the issue with modification time, and - // https://github.com/moby/moby/pull/11422 for the change. - // (Note that in the Windows implementation of this function, - // modification time IS taken as a change). See - // https://github.com/moby/moby/pull/37982 for more information. - (!oldStat.Mode().IsDir() && - (!sameFsTime(oldStat.ModTime(), newStat.ModTime()) || (oldStat.Size() != newStat.Size()))) { - return true - } - return false -} - -func (info *FileInfo) isDir() bool { - return info.parent == nil || info.stat.Mode().IsDir() -} - -func getIno(fi os.FileInfo) uint64 { - return fi.Sys().(*syscall.Stat_t).Ino -} - -func hasHardlinks(fi os.FileInfo) bool { - return fi.Sys().(*syscall.Stat_t).Nlink > 1 -} diff --git a/vendor/github.com/moby/go-archive/changes_windows.go b/vendor/github.com/moby/go-archive/changes_windows.go deleted file mode 100644 index c89605c78fed..000000000000 --- a/vendor/github.com/moby/go-archive/changes_windows.go +++ /dev/null @@ -1,33 +0,0 @@ -package archive - -import ( - "io/fs" - "os" -) - -func statDifferent(oldStat fs.FileInfo, newStat fs.FileInfo) bool { - // Note there is slight difference between the Linux and Windows - // implementations here. Due to https://github.com/moby/moby/issues/9874, - // and the fix at https://github.com/moby/moby/pull/11422, Linux does not - // consider a change to the directory time as a change. Windows on NTFS - // does. See https://github.com/moby/moby/pull/37982 for more information. - - if !sameFsTime(oldStat.ModTime(), newStat.ModTime()) || - oldStat.Mode() != newStat.Mode() || - oldStat.Size() != newStat.Size() && !oldStat.Mode().IsDir() { - return true - } - return false -} - -func (info *FileInfo) isDir() bool { - return info.parent == nil || info.stat.Mode().IsDir() -} - -func getIno(fi os.FileInfo) (inode uint64) { - return -} - -func hasHardlinks(fi os.FileInfo) bool { - return false -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive.go b/vendor/github.com/moby/go-archive/chrootarchive/archive.go deleted file mode 100644 index 26da8e2924ba..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive.go +++ /dev/null @@ -1,98 +0,0 @@ -package chrootarchive - -import ( - "errors" - "io" - "os" - "path/filepath" - - "github.com/moby/sys/user" - - "github.com/moby/go-archive" - "github.com/moby/go-archive/compression" -) - -// NewArchiver returns a new Archiver which uses chrootarchive.Untar -func NewArchiver(idMapping user.IdentityMapping) *archive.Archiver { - return &archive.Archiver{ - Untar: Untar, - IDMapping: idMapping, - } -} - -// Untar reads a stream of bytes from `archive`, parses it as a tar archive, -// and unpacks it into the directory at `dest`. -// The archive may be compressed with one of the following algorithms: -// identity (uncompressed), gzip, bzip2, xz. -func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error { - return untarHandler(tarArchive, dest, options, true, dest) -} - -// UntarWithRoot is the same as `Untar`, but allows you to pass in a root directory -// The root directory is the directory that will be chrooted to. -// `dest` must be a path within `root`, if it is not an error will be returned. -// -// `root` should set to a directory which is not controlled by any potentially -// malicious process. -// -// This should be used to prevent a potential attacker from manipulating `dest` -// such that it would provide access to files outside of `dest` through things -// like symlinks. Normally `ResolveSymlinksInScope` would handle this, however -// sanitizing symlinks in this manner is inherently racey: -// ref: CVE-2018-15664 -func UntarWithRoot(tarArchive io.Reader, dest string, options *archive.TarOptions, root string) error { - return untarHandler(tarArchive, dest, options, true, root) -} - -// UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive, -// and unpacks it into the directory at `dest`. -// The archive must be an uncompressed stream. -func UntarUncompressed(tarArchive io.Reader, dest string, options *archive.TarOptions) error { - return untarHandler(tarArchive, dest, options, false, dest) -} - -// Handler for teasing out the automatic decompression -func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions, decompress bool, root string) error { - if tarArchive == nil { - return errors.New("empty archive") - } - if options == nil { - options = &archive.TarOptions{} - } - if options.ExcludePatterns == nil { - options.ExcludePatterns = []string{} - } - - // If dest is inside a root then directory is created within chroot by extractor. - // This case is only currently used by cp. - if dest == root { - uid, gid := options.IDMap.RootPair() - - dest = filepath.Clean(dest) - if _, err := os.Stat(dest); os.IsNotExist(err) { - if err := user.MkdirAllAndChown(dest, 0o755, uid, gid, user.WithOnlyNew); err != nil { - return err - } - } - } - - r := io.NopCloser(tarArchive) - if decompress { - decompressedArchive, err := compression.DecompressStream(tarArchive) - if err != nil { - return err - } - defer decompressedArchive.Close() - r = decompressedArchive - } - - return invokeUnpack(r, dest, options, root) -} - -// Tar tars the requested path while chrooted to the specified root. -func Tar(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) { - if options == nil { - options = &archive.TarOptions{} - } - return invokePack(srcPath, options, root) -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive_linux.go b/vendor/github.com/moby/go-archive/chrootarchive/archive_linux.go deleted file mode 100644 index a3b73676d219..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive_linux.go +++ /dev/null @@ -1,54 +0,0 @@ -package chrootarchive - -import ( - "fmt" - "io" - - "golang.org/x/sys/unix" - - "github.com/moby/go-archive" -) - -func doUnpack(decompressedArchive io.Reader, relDest, root string, options *archive.TarOptions) error { - done := make(chan error) - err := goInChroot(root, func() { done <- archive.Unpack(decompressedArchive, relDest, options) }) - if err != nil { - return err - } - return <-done -} - -func doPack(relSrc, root string, options *archive.TarOptions) (io.ReadCloser, error) { - tb, err := archive.NewTarballer(relSrc, options) - if err != nil { - return nil, fmt.Errorf("error processing tar file: %w", err) - } - err = goInChroot(root, tb.Do) - if err != nil { - return nil, fmt.Errorf("could not chroot: %w", err) - } - return tb.Reader(), nil -} - -func doUnpackLayer(root string, layer io.Reader, options *archive.TarOptions) (int64, error) { - type result struct { - layerSize int64 - err error - } - done := make(chan result) - - err := goInChroot(root, func() { - // We need to be able to set any perms - _ = unix.Umask(0) - - size, err := archive.UnpackLayer("/", layer, options) - done <- result{layerSize: size, err: err} - }) - if err != nil { - return 0, err - } - - res := <-done - - return res.layerSize, res.err -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive_unix.go b/vendor/github.com/moby/go-archive/chrootarchive/archive_unix.go deleted file mode 100644 index e2b8d5016acf..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive_unix.go +++ /dev/null @@ -1,60 +0,0 @@ -//go:build !windows - -package chrootarchive - -import ( - "errors" - "io" - "path/filepath" - "strings" - - "github.com/moby/go-archive" -) - -func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions, root string) error { - relDest, err := resolvePathInChroot(root, dest) - if err != nil { - return err - } - - return doUnpack(decompressedArchive, relDest, root, options) -} - -func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) { - relSrc, err := resolvePathInChroot(root, srcPath) - if err != nil { - return nil, err - } - - // make sure we didn't trim a trailing slash with the call to `resolvePathInChroot` - if strings.HasSuffix(srcPath, "/") && !strings.HasSuffix(relSrc, "/") { - relSrc += "/" - } - - return doPack(relSrc, root, options) -} - -// resolvePathInChroot returns the equivalent to path inside a chroot rooted at root. -// The returned path always begins with '/'. -// -// - resolvePathInChroot("/a/b", "/a/b/c/d") -> "/c/d" -// - resolvePathInChroot("/a/b", "/a/b") -> "/" -// -// The implementation is buggy, and some bugs may be load-bearing. -// Here be dragons. -func resolvePathInChroot(root, path string) (string, error) { - if root == "" { - return "", errors.New("root path must not be empty") - } - rel, err := filepath.Rel(root, path) - if err != nil { - return "", err - } - if rel == "." { - rel = "/" - } - if rel[0] != '/' { - rel = "/" + rel - } - return rel, nil -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive_unix_nolinux.go b/vendor/github.com/moby/go-archive/chrootarchive/archive_unix_nolinux.go deleted file mode 100644 index f5b02ea55068..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive_unix_nolinux.go +++ /dev/null @@ -1,226 +0,0 @@ -//go:build unix && !linux - -package chrootarchive - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "os" - "syscall" - - "github.com/moby/sys/reexec" - "golang.org/x/sys/unix" - - "github.com/moby/go-archive" -) - -const ( - packCmd = "chrootarchive-pack-in-chroot" - unpackCmd = "chrootarchive-unpack-in-chroot" - unpackLayerCmd = "chrootarchive-unpack-layer-in-chroot" -) - -func init() { - reexec.Register(packCmd, reexecMain(packInChroot)) - reexec.Register(unpackCmd, reexecMain(unpackInChroot)) - reexec.Register(unpackLayerCmd, reexecMain(unpackLayerInChroot)) -} - -func reexecMain(f func(options archive.TarOptions, args ...string) error) func() { - return func() { - if len(os.Args) < 2 { - fmt.Fprintln(os.Stderr, "root parameter is required") - os.Exit(1) - } - - options, err := recvOptions() - root := os.Args[1] - - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - - if err := syscall.Chroot(root); err != nil { - fmt.Fprintln( - os.Stderr, - os.PathError{Op: "chroot", Path: root, Err: err}, - ) - os.Exit(2) - } - - if err := f(*options, os.Args[2:]...); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(3) - } - } -} - -func doUnpack(decompressedArchive io.Reader, relDest, root string, options *archive.TarOptions) error { - optionsR, optionsW, err := os.Pipe() - if err != nil { - return err - } - defer optionsW.Close() - defer optionsR.Close() - - stderr := bytes.NewBuffer(nil) - - cmd := reexec.Command(unpackCmd, root, relDest) - cmd.Stdin = decompressedArchive - cmd.Stderr = stderr - cmd.ExtraFiles = []*os.File{ - optionsR, - } - - if err = cmd.Start(); err != nil { - return fmt.Errorf("re-exec error: %w", err) - } - - if err = json.NewEncoder(optionsW).Encode(options); err != nil { - return fmt.Errorf("tar options encoding failed: %w", err) - } - - if err = cmd.Wait(); err != nil { - return fmt.Errorf("%s: %w", stderr.String(), err) - } - - return nil -} - -func doPack(relSrc, root string, options *archive.TarOptions) (io.ReadCloser, error) { - optionsR, optionsW, err := os.Pipe() - if err != nil { - return nil, err - } - defer optionsW.Close() - defer optionsR.Close() - - stderr := bytes.NewBuffer(nil) - cmd := reexec.Command(packCmd, root, relSrc) - cmd.ExtraFiles = []*os.File{ - optionsR, - } - cmd.Stderr = stderr - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - - r, w := io.Pipe() - - if err = cmd.Start(); err != nil { - return nil, fmt.Errorf("re-exec error: %w", err) - } - - go func() { - _, _ = io.Copy(w, stdout) - // Cleanup once stdout pipe is closed. - if err = cmd.Wait(); err != nil { - r.CloseWithError(fmt.Errorf("%s: %w", stderr.String(), err)) - } else { - r.Close() - } - }() - - if err = json.NewEncoder(optionsW).Encode(options); err != nil { - return nil, fmt.Errorf("tar options encoding failed: %w", err) - } - - return r, nil -} - -func doUnpackLayer(root string, layer io.Reader, options *archive.TarOptions) (int64, error) { - var result int64 - optionsR, optionsW, err := os.Pipe() - if err != nil { - return 0, err - } - defer optionsW.Close() - defer optionsR.Close() - buffer := bytes.NewBuffer(nil) - - cmd := reexec.Command(unpackLayerCmd, root) - cmd.Stdin = layer - cmd.Stdout = buffer - cmd.Stderr = buffer - cmd.ExtraFiles = []*os.File{ - optionsR, - } - - if err = cmd.Start(); err != nil { - return 0, fmt.Errorf("re-exec error: %w", err) - } - - if err = json.NewEncoder(optionsW).Encode(options); err != nil { - return 0, fmt.Errorf("tar options encoding failed: %w", err) - } - - if err = cmd.Wait(); err != nil { - return 0, fmt.Errorf("%s: %w", buffer.String(), err) - } - - if err = json.NewDecoder(buffer).Decode(&result); err != nil { - return 0, fmt.Errorf("json decoding error: %w", err) - } - - return result, nil -} - -func unpackInChroot(options archive.TarOptions, args ...string) error { - if len(args) < 1 { - return fmt.Errorf("destination parameter is required") - } - - relDest := args[0] - - return archive.Unpack(os.Stdin, relDest, &options) -} - -func packInChroot(options archive.TarOptions, args ...string) error { - if len(args) < 1 { - return fmt.Errorf("source parameter is required") - } - - relSrc := args[0] - - tb, err := archive.NewTarballer(relSrc, &options) - if err != nil { - return err - } - - go tb.Do() - - _, err = io.Copy(os.Stdout, tb.Reader()) - - return err -} - -func unpackLayerInChroot(options archive.TarOptions, _args ...string) error { - // We need to be able to set any perms - _ = unix.Umask(0) - - size, err := archive.UnpackLayer("/", os.Stdin, &options) - if err != nil { - return err - } - - return json.NewEncoder(os.Stdout).Encode(size) -} - -func recvOptions() (*archive.TarOptions, error) { - var options archive.TarOptions - optionsPipe := os.NewFile(3, "tar-options") - if optionsPipe == nil { - return nil, fmt.Errorf("could not read tar options from the pipe") - } - defer optionsPipe.Close() - err := json.NewDecoder(optionsPipe).Decode(&options) - if err != nil { - return &options, err - } - - return &options, nil -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/archive_windows.go b/vendor/github.com/moby/go-archive/chrootarchive/archive_windows.go deleted file mode 100644 index 078341f57f95..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/archive_windows.go +++ /dev/null @@ -1,40 +0,0 @@ -package chrootarchive - -import ( - "io" - "strings" - - "github.com/moby/go-archive" -) - -// longPathPrefix is the longpath prefix for Windows file paths. -const longPathPrefix = `\\?\` - -// addLongPathPrefix adds the Windows long path prefix to the path provided if -// it does not already have it. It is a no-op on platforms other than Windows. -// -// addLongPathPrefix is a copy of [github.com/docker/docker/pkg/longpath.AddPrefix]. -func addLongPathPrefix(srcPath string) string { - if strings.HasPrefix(srcPath, longPathPrefix) { - return srcPath - } - if strings.HasPrefix(srcPath, `\\`) { - // This is a UNC path, so we need to add 'UNC' to the path as well. - return longPathPrefix + `UNC` + srcPath[1:] - } - return longPathPrefix + srcPath -} - -func invokeUnpack(decompressedArchive io.ReadCloser, dest string, options *archive.TarOptions, root string) error { - // Windows is different to Linux here because Windows does not support - // chroot. Hence there is no point sandboxing a chrooted process to - // do the unpack. We call inline instead within the daemon process. - return archive.Unpack(decompressedArchive, addLongPathPrefix(dest), options) -} - -func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) { - // Windows is different to Linux here because Windows does not support - // chroot. Hence there is no point sandboxing a chrooted process to - // do the pack. We call inline instead within the daemon process. - return archive.TarWithOptions(srcPath, options) -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/chroot_linux.go b/vendor/github.com/moby/go-archive/chrootarchive/chroot_linux.go deleted file mode 100644 index 3d9790be4676..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/chroot_linux.go +++ /dev/null @@ -1,35 +0,0 @@ -package chrootarchive - -import ( - "github.com/moby/sys/mount" - "golang.org/x/sys/unix" - - "github.com/moby/go-archive/internal/mounttree" - "github.com/moby/go-archive/internal/unshare" -) - -// goInChroot starts fn in a goroutine where the root directory, current working -// directory and umask are unshared from other goroutines and the root directory -// has been changed to path. These changes are only visible to the goroutine in -// which fn is executed. Any other goroutines, including ones started from fn, -// will see the same root directory and file system attributes as the rest of -// the process. -func goInChroot(path string, fn func()) error { - return unshare.Go( - unix.CLONE_FS|unix.CLONE_NEWNS, - func() error { - // Make everything in new ns slave. - // Don't use `private` here as this could race where the mountns gets a - // reference to a mount and an unmount from the host does not propagate, - // which could potentially cause transient errors for other operations, - // even though this should be relatively small window here `slave` should - // not cause any problems. - if err := mount.MakeRSlave("/"); err != nil { - return err - } - - return mounttree.SwitchRoot(path) - }, - fn, - ) -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/diff.go b/vendor/github.com/moby/go-archive/chrootarchive/diff.go deleted file mode 100644 index c09fa5b0d0d4..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/diff.go +++ /dev/null @@ -1,23 +0,0 @@ -package chrootarchive - -import ( - "io" - - "github.com/moby/go-archive" -) - -// ApplyLayer parses a diff in the standard layer format from `layer`, -// and applies it to the directory `dest`. The stream `layer` can only be -// uncompressed. -// Returns the size in bytes of the contents of the layer. -func ApplyLayer(dest string, layer io.Reader) (size int64, err error) { - return applyLayerHandler(dest, layer, &archive.TarOptions{}, true) -} - -// ApplyUncompressedLayer parses a diff in the standard layer format from -// `layer`, and applies it to the directory `dest`. The stream `layer` -// can only be uncompressed. -// Returns the size in bytes of the contents of the layer. -func ApplyUncompressedLayer(dest string, layer io.Reader, options *archive.TarOptions) (int64, error) { - return applyLayerHandler(dest, layer, options, false) -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/diff_unix.go b/vendor/github.com/moby/go-archive/chrootarchive/diff_unix.go deleted file mode 100644 index 0e95bcd95bc3..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/diff_unix.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build !windows - -package chrootarchive - -import ( - "io" - "path/filepath" - - "github.com/moby/sys/userns" - - "github.com/moby/go-archive" - "github.com/moby/go-archive/compression" -) - -// applyLayerHandler parses a diff in the standard layer format from `layer`, and -// applies it to the directory `dest`. Returns the size in bytes of the -// contents of the layer. -func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions, decompress bool) (size int64, _ error) { - if decompress { - decompressed, err := compression.DecompressStream(layer) - if err != nil { - return 0, err - } - defer decompressed.Close() - - layer = decompressed - } - if options == nil { - options = &archive.TarOptions{} - } - if userns.RunningInUserNS() { - options.InUserNS = true - } - if options.ExcludePatterns == nil { - options.ExcludePatterns = []string{} - } - dest = filepath.Clean(dest) - return doUnpackLayer(dest, layer, options) -} diff --git a/vendor/github.com/moby/go-archive/chrootarchive/diff_windows.go b/vendor/github.com/moby/go-archive/chrootarchive/diff_windows.go deleted file mode 100644 index 7e0cb5490f14..000000000000 --- a/vendor/github.com/moby/go-archive/chrootarchive/diff_windows.go +++ /dev/null @@ -1,34 +0,0 @@ -package chrootarchive - -import ( - "fmt" - "io" - "path/filepath" - - "github.com/moby/go-archive" - "github.com/moby/go-archive/compression" -) - -// applyLayerHandler parses a diff in the standard layer format from `layer`, and -// applies it to the directory `dest`. Returns the size in bytes of the -// contents of the layer. -func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) { - if decompress { - decompressed, err := compression.DecompressStream(layer) - if err != nil { - return 0, err - } - defer decompressed.Close() - - layer = decompressed - } - - // Ensure it is a Windows-style volume path - dest = addLongPathPrefix(filepath.Clean(dest)) - s, err := archive.UnpackLayer(dest, layer, nil) - if err != nil { - return 0, fmt.Errorf("ApplyLayer %s failed UnpackLayer to %s: %w", layer, dest, err) - } - - return s, nil -} diff --git a/vendor/github.com/moby/go-archive/compression/compression.go b/vendor/github.com/moby/go-archive/compression/compression.go deleted file mode 100644 index e298cefb3249..000000000000 --- a/vendor/github.com/moby/go-archive/compression/compression.go +++ /dev/null @@ -1,263 +0,0 @@ -package compression - -import ( - "bufio" - "bytes" - "compress/bzip2" - "compress/gzip" - "context" - "errors" - "fmt" - "io" - "os" - "os/exec" - "strconv" - "sync" - - "github.com/containerd/log" - "github.com/klauspost/compress/zstd" -) - -// Compression is the state represents if compressed or not. -type Compression int - -const ( - None Compression = 0 // None represents the uncompressed. - Bzip2 Compression = 1 // Bzip2 is bzip2 compression algorithm. - Gzip Compression = 2 // Gzip is gzip compression algorithm. - Xz Compression = 3 // Xz is xz compression algorithm. - Zstd Compression = 4 // Zstd is zstd compression algorithm. -) - -// Extension returns the extension of a file that uses the specified compression algorithm. -func (c *Compression) Extension() string { - switch *c { - case None: - return "tar" - case Bzip2: - return "tar.bz2" - case Gzip: - return "tar.gz" - case Xz: - return "tar.xz" - case Zstd: - return "tar.zst" - default: - return "" - } -} - -type readCloserWrapper struct { - io.Reader - closer func() error -} - -func (r *readCloserWrapper) Close() error { - if r.closer != nil { - return r.closer() - } - return nil -} - -type nopWriteCloser struct { - io.Writer -} - -func (nopWriteCloser) Close() error { return nil } - -var bufioReader32KPool = &sync.Pool{ - New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) }, -} - -type bufferedReader struct { - buf *bufio.Reader -} - -func newBufferedReader(r io.Reader) *bufferedReader { - buf := bufioReader32KPool.Get().(*bufio.Reader) - buf.Reset(r) - return &bufferedReader{buf} -} - -func (r *bufferedReader) Read(p []byte) (int, error) { - if r.buf == nil { - return 0, io.EOF - } - n, err := r.buf.Read(p) - if errors.Is(err, io.EOF) { - r.buf.Reset(nil) - bufioReader32KPool.Put(r.buf) - r.buf = nil - } - return n, err -} - -func (r *bufferedReader) Peek(n int) ([]byte, error) { - if r.buf == nil { - return nil, io.EOF - } - return r.buf.Peek(n) -} - -// DecompressStream decompresses the archive and returns a ReaderCloser with the decompressed archive. -func DecompressStream(archive io.Reader) (io.ReadCloser, error) { - buf := newBufferedReader(archive) - bs, err := buf.Peek(10) - if err != nil && !errors.Is(err, io.EOF) { - // Note: we'll ignore any io.EOF error because there are some odd - // cases where the layer.tar file will be empty (zero bytes) and - // that results in an io.EOF from the Peek() call. So, in those - // cases we'll just treat it as a non-compressed stream and - // that means just create an empty layer. - // See Issue 18170 - return nil, err - } - - switch compression := Detect(bs); compression { - case None: - return &readCloserWrapper{ - Reader: buf, - }, nil - case Gzip: - ctx, cancel := context.WithCancel(context.Background()) - gzReader, err := gzipDecompress(ctx, buf) - if err != nil { - cancel() - return nil, err - } - - return &readCloserWrapper{ - Reader: gzReader, - closer: func() error { - cancel() - return gzReader.Close() - }, - }, nil - case Bzip2: - bz2Reader := bzip2.NewReader(buf) - return &readCloserWrapper{ - Reader: bz2Reader, - }, nil - case Xz: - ctx, cancel := context.WithCancel(context.Background()) - - xzReader, err := xzDecompress(ctx, buf) - if err != nil { - cancel() - return nil, err - } - - return &readCloserWrapper{ - Reader: xzReader, - closer: func() error { - cancel() - return xzReader.Close() - }, - }, nil - case Zstd: - zstdReader, err := zstd.NewReader(buf) - if err != nil { - return nil, err - } - return &readCloserWrapper{ - Reader: zstdReader, - closer: func() error { - zstdReader.Close() - return nil - }, - }, nil - - default: - return nil, fmt.Errorf("unsupported compression format (%d)", compression) - } -} - -// CompressStream compresses the dest with specified compression algorithm. -func CompressStream(dest io.Writer, compression Compression) (io.WriteCloser, error) { - switch compression { - case None: - return nopWriteCloser{dest}, nil - case Gzip: - return gzip.NewWriter(dest), nil - case Bzip2: - // archive/bzip2 does not support writing. - return nil, errors.New("unsupported compression format: tar.bz2") - case Xz: - // there is no xz support at all - // However, this is not a problem as docker only currently generates gzipped tars - return nil, errors.New("unsupported compression format: tar.xz") - default: - return nil, fmt.Errorf("unsupported compression format (%d)", compression) - } -} - -func xzDecompress(ctx context.Context, archive io.Reader) (io.ReadCloser, error) { - args := []string{"xz", "-d", "-c", "-q"} - - return cmdStream(exec.CommandContext(ctx, args[0], args[1:]...), archive) -} - -func gzipDecompress(ctx context.Context, buf io.Reader) (io.ReadCloser, error) { - if noPigzEnv := os.Getenv("MOBY_DISABLE_PIGZ"); noPigzEnv != "" { - noPigz, err := strconv.ParseBool(noPigzEnv) - if err != nil { - log.G(ctx).WithError(err).Warn("invalid value in MOBY_DISABLE_PIGZ env var") - } - if noPigz { - log.G(ctx).Debugf("Use of pigz is disabled due to MOBY_DISABLE_PIGZ=%s", noPigzEnv) - return gzip.NewReader(buf) - } - } - - unpigzPath, err := exec.LookPath("unpigz") - if err != nil { - log.G(ctx).Debugf("unpigz binary not found, falling back to go gzip library") - return gzip.NewReader(buf) - } - - log.G(ctx).Debugf("Using %s to decompress", unpigzPath) - - return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) -} - -// cmdStream executes a command, and returns its stdout as a stream. -// If the command fails to run or doesn't complete successfully, an error -// will be returned, including anything written on stderr. -func cmdStream(cmd *exec.Cmd, in io.Reader) (io.ReadCloser, error) { - reader, writer := io.Pipe() - - cmd.Stdin = in - cmd.Stdout = writer - - var errBuf bytes.Buffer - cmd.Stderr = &errBuf - - // Run the command and return the pipe - if err := cmd.Start(); err != nil { - return nil, err - } - - // Ensure the command has exited before we clean anything up - done := make(chan struct{}) - - // Copy stdout to the returned pipe - go func() { - if err := cmd.Wait(); err != nil { - _ = writer.CloseWithError(fmt.Errorf("%w: %s", err, errBuf.String())) - } else { - _ = writer.Close() - } - close(done) - }() - - return &readCloserWrapper{ - Reader: reader, - closer: func() error { - // Close pipeR, and then wait for the command to complete before returning. We have to close pipeR first, as - // cmd.Wait waits for any non-file stdout/stderr/stdin to close. - err := reader.Close() - <-done - return err - }, - }, nil -} diff --git a/vendor/github.com/moby/go-archive/compression/compression_detect.go b/vendor/github.com/moby/go-archive/compression/compression_detect.go deleted file mode 100644 index 85eda927ccfb..000000000000 --- a/vendor/github.com/moby/go-archive/compression/compression_detect.go +++ /dev/null @@ -1,65 +0,0 @@ -package compression - -import ( - "bytes" - "encoding/binary" -) - -const ( - zstdMagicSkippableStart = 0x184D2A50 - zstdMagicSkippableMask = 0xFFFFFFF0 -) - -var ( - bzip2Magic = []byte{0x42, 0x5A, 0x68} - gzipMagic = []byte{0x1F, 0x8B, 0x08} - xzMagic = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00} - zstdMagic = []byte{0x28, 0xb5, 0x2f, 0xfd} -) - -type matcher = func([]byte) bool - -// Detect detects the compression algorithm of the source. -func Detect(source []byte) Compression { - compressionMap := map[Compression]matcher{ - Bzip2: magicNumberMatcher(bzip2Magic), - Gzip: magicNumberMatcher(gzipMagic), - Xz: magicNumberMatcher(xzMagic), - Zstd: zstdMatcher(), - } - for _, compression := range []Compression{Bzip2, Gzip, Xz, Zstd} { - fn := compressionMap[compression] - if fn(source) { - return compression - } - } - return None -} - -func magicNumberMatcher(m []byte) matcher { - return func(source []byte) bool { - return bytes.HasPrefix(source, m) - } -} - -// zstdMatcher detects zstd compression algorithm. -// Zstandard compressed data is made of one or more frames. -// There are two frame formats defined by Zstandard: Zstandard frames and Skippable frames. -// See https://datatracker.ietf.org/doc/html/rfc8878#section-3 for more details. -func zstdMatcher() matcher { - return func(source []byte) bool { - if bytes.HasPrefix(source, zstdMagic) { - // Zstandard frame - return true - } - // skippable frame - if len(source) < 8 { - return false - } - // magic number from 0x184D2A50 to 0x184D2A5F. - if binary.LittleEndian.Uint32(source[:4])&zstdMagicSkippableMask == zstdMagicSkippableStart { - return true - } - return false - } -} diff --git a/vendor/github.com/moby/go-archive/copy.go b/vendor/github.com/moby/go-archive/copy.go deleted file mode 100644 index 77d038c42310..000000000000 --- a/vendor/github.com/moby/go-archive/copy.go +++ /dev/null @@ -1,496 +0,0 @@ -package archive - -import ( - "archive/tar" - "context" - "errors" - "io" - "os" - "path/filepath" - "strings" - "sync" - - "github.com/containerd/log" -) - -// Errors used or returned by this file. -var ( - ErrNotDirectory = errors.New("not a directory") - ErrDirNotExists = errors.New("no such directory") - ErrCannotCopyDir = errors.New("cannot copy directory") - ErrInvalidCopySource = errors.New("invalid copy source content") -) - -var copyPool = sync.Pool{ - New: func() interface{} { s := make([]byte, 32*1024); return &s }, -} - -func copyWithBuffer(dst io.Writer, src io.Reader) error { - buf := copyPool.Get().(*[]byte) - _, err := io.CopyBuffer(dst, src, *buf) - copyPool.Put(buf) - return err -} - -// PreserveTrailingDotOrSeparator returns the given cleaned path (after -// processing using any utility functions from the path or filepath stdlib -// packages) and appends a trailing `/.` or `/` if its corresponding original -// path (from before being processed by utility functions from the path or -// filepath stdlib packages) ends with a trailing `/.` or `/`. If the cleaned -// path already ends in a `.` path segment, then another is not added. If the -// clean path already ends in a path separator, then another is not added. -func PreserveTrailingDotOrSeparator(cleanedPath string, originalPath string) string { - // Ensure paths are in platform semantics - cleanedPath = normalizePath(cleanedPath) - originalPath = normalizePath(originalPath) - - if !specifiesCurrentDir(cleanedPath) && specifiesCurrentDir(originalPath) { - if !hasTrailingPathSeparator(cleanedPath) { - // Add a separator if it doesn't already end with one (a cleaned - // path would only end in a separator if it is the root). - cleanedPath += string(filepath.Separator) - } - cleanedPath += "." - } - - if !hasTrailingPathSeparator(cleanedPath) && hasTrailingPathSeparator(originalPath) { - cleanedPath += string(filepath.Separator) - } - - return cleanedPath -} - -// assertsDirectory returns whether the given path is -// asserted to be a directory, i.e., the path ends with -// a trailing '/' or `/.`, assuming a path separator of `/`. -func assertsDirectory(path string) bool { - return hasTrailingPathSeparator(path) || specifiesCurrentDir(path) -} - -// hasTrailingPathSeparator returns whether the given -// path ends with the system's path separator character. -func hasTrailingPathSeparator(path string) bool { - return len(path) > 0 && path[len(path)-1] == filepath.Separator -} - -// specifiesCurrentDir returns whether the given path specifies -// a "current directory", i.e., the last path segment is `.`. -func specifiesCurrentDir(path string) bool { - return filepath.Base(path) == "." -} - -// SplitPathDirEntry splits the given path between its directory name and its -// basename by first cleaning the path but preserves a trailing "." if the -// original path specified the current directory. -func SplitPathDirEntry(path string) (dir, base string) { - cleanedPath := filepath.Clean(filepath.FromSlash(path)) - - if specifiesCurrentDir(path) { - cleanedPath += string(os.PathSeparator) + "." - } - - return filepath.Dir(cleanedPath), filepath.Base(cleanedPath) -} - -// TarResource archives the resource described by the given CopyInfo to a Tar -// archive. A non-nil error is returned if sourcePath does not exist or is -// asserted to be a directory but exists as another type of file. -// -// This function acts as a convenient wrapper around TarWithOptions, which -// requires a directory as the source path. TarResource accepts either a -// directory or a file path and correctly sets the Tar options. -func TarResource(sourceInfo CopyInfo) (content io.ReadCloser, err error) { - return TarResourceRebase(sourceInfo.Path, sourceInfo.RebaseName) -} - -// TarResourceRebase is like TarResource but renames the first path element of -// items in the resulting tar archive to match the given rebaseName if not "". -func TarResourceRebase(sourcePath, rebaseName string) (content io.ReadCloser, _ error) { - sourcePath = normalizePath(sourcePath) - if _, err := os.Lstat(sourcePath); err != nil { - // Catches the case where the source does not exist or is not a - // directory if asserted to be a directory, as this also causes an - // error. - return nil, err - } - - // Separate the source path between its directory and - // the entry in that directory which we are archiving. - sourceDir, sourceBase := SplitPathDirEntry(sourcePath) - opts := TarResourceRebaseOpts(sourceBase, rebaseName) - - log.G(context.TODO()).Debugf("copying %q from %q", sourceBase, sourceDir) - return TarWithOptions(sourceDir, opts) -} - -// TarResourceRebaseOpts does not preform the Tar, but instead just creates the rebase -// parameters to be sent to TarWithOptions (the TarOptions struct) -func TarResourceRebaseOpts(sourceBase string, rebaseName string) *TarOptions { - filter := []string{sourceBase} - return &TarOptions{ - IncludeFiles: filter, - IncludeSourceDir: true, - RebaseNames: map[string]string{ - sourceBase: rebaseName, - }, - } -} - -// CopyInfo holds basic info about the source -// or destination path of a copy operation. -type CopyInfo struct { - Path string - Exists bool - IsDir bool - RebaseName string -} - -// CopyInfoSourcePath stats the given path to create a CopyInfo -// struct representing that resource for the source of an archive copy -// operation. The given path should be an absolute local path. A source path -// has all symlinks evaluated that appear before the last path separator ("/" -// on Unix). As it is to be a copy source, the path must exist. -func CopyInfoSourcePath(path string, followLink bool) (CopyInfo, error) { - // normalize the file path and then evaluate the symbol link - // we will use the target file instead of the symbol link if - // followLink is set - path = normalizePath(path) - - resolvedPath, rebaseName, err := ResolveHostSourcePath(path, followLink) - if err != nil { - return CopyInfo{}, err - } - - stat, err := os.Lstat(resolvedPath) - if err != nil { - return CopyInfo{}, err - } - - return CopyInfo{ - Path: resolvedPath, - Exists: true, - IsDir: stat.IsDir(), - RebaseName: rebaseName, - }, nil -} - -// CopyInfoDestinationPath stats the given path to create a CopyInfo -// struct representing that resource for the destination of an archive copy -// operation. The given path should be an absolute local path. -func CopyInfoDestinationPath(path string) (info CopyInfo, err error) { - maxSymlinkIter := 10 // filepath.EvalSymlinks uses 255, but 10 already seems like a lot. - path = normalizePath(path) - originalPath := path - - stat, err := os.Lstat(path) - - if err == nil && stat.Mode()&os.ModeSymlink == 0 { - // The path exists and is not a symlink. - return CopyInfo{ - Path: path, - Exists: true, - IsDir: stat.IsDir(), - }, nil - } - - // While the path is a symlink. - for n := 0; err == nil && stat.Mode()&os.ModeSymlink != 0; n++ { - if n > maxSymlinkIter { - // Don't follow symlinks more than this arbitrary number of times. - return CopyInfo{}, errors.New("too many symlinks in " + originalPath) - } - - // The path is a symbolic link. We need to evaluate it so that the - // destination of the copy operation is the link target and not the - // link itself. This is notably different than CopyInfoSourcePath which - // only evaluates symlinks before the last appearing path separator. - // Also note that it is okay if the last path element is a broken - // symlink as the copy operation should create the target. - var linkTarget string - - linkTarget, err = os.Readlink(path) - if err != nil { - return CopyInfo{}, err - } - - if !filepath.IsAbs(linkTarget) { - // Join with the parent directory. - dstParent, _ := SplitPathDirEntry(path) - linkTarget = filepath.Join(dstParent, linkTarget) - } - - path = linkTarget - stat, err = os.Lstat(path) - } - - if err != nil { - // It's okay if the destination path doesn't exist. We can still - // continue the copy operation if the parent directory exists. - if !os.IsNotExist(err) { - return CopyInfo{}, err - } - - // Ensure destination parent dir exists. - dstParent, _ := SplitPathDirEntry(path) - - parentDirStat, err := os.Stat(dstParent) - if err != nil { - return CopyInfo{}, err - } - if !parentDirStat.IsDir() { - return CopyInfo{}, ErrNotDirectory - } - - return CopyInfo{Path: path}, nil - } - - // The path exists after resolving symlinks. - return CopyInfo{ - Path: path, - Exists: true, - IsDir: stat.IsDir(), - }, nil -} - -// PrepareArchiveCopy prepares the given srcContent archive, which should -// contain the archived resource described by srcInfo, to the destination -// described by dstInfo. Returns the possibly modified content archive along -// with the path to the destination directory which it should be extracted to. -func PrepareArchiveCopy(srcContent io.Reader, srcInfo, dstInfo CopyInfo) (dstDir string, content io.ReadCloser, err error) { - // Ensure in platform semantics - srcInfo.Path = normalizePath(srcInfo.Path) - dstInfo.Path = normalizePath(dstInfo.Path) - - // Separate the destination path between its directory and base - // components in case the source archive contents need to be rebased. - dstDir, dstBase := SplitPathDirEntry(dstInfo.Path) - _, srcBase := SplitPathDirEntry(srcInfo.Path) - - switch { - case dstInfo.Exists && dstInfo.IsDir: - // The destination exists as a directory. No alteration - // to srcContent is needed as its contents can be - // simply extracted to the destination directory. - return dstInfo.Path, io.NopCloser(srcContent), nil - case dstInfo.Exists && srcInfo.IsDir: - // The destination exists as some type of file and the source - // content is a directory. This is an error condition since - // you cannot copy a directory to an existing file location. - return "", nil, ErrCannotCopyDir - case dstInfo.Exists: - // The destination exists as some type of file and the source content - // is also a file. The source content entry will have to be renamed to - // have a basename which matches the destination path's basename. - if len(srcInfo.RebaseName) != 0 { - srcBase = srcInfo.RebaseName - } - return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil - case srcInfo.IsDir: - // The destination does not exist and the source content is an archive - // of a directory. The archive should be extracted to the parent of - // the destination path instead, and when it is, the directory that is - // created as a result should take the name of the destination path. - // The source content entries will have to be renamed to have a - // basename which matches the destination path's basename. - if len(srcInfo.RebaseName) != 0 { - srcBase = srcInfo.RebaseName - } - return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil - case assertsDirectory(dstInfo.Path): - // The destination does not exist and is asserted to be created as a - // directory, but the source content is not a directory. This is an - // error condition since you cannot create a directory from a file - // source. - return "", nil, ErrDirNotExists - default: - // The last remaining case is when the destination does not exist, is - // not asserted to be a directory, and the source content is not an - // archive of a directory. It this case, the destination file will need - // to be created when the archive is extracted and the source content - // entry will have to be renamed to have a basename which matches the - // destination path's basename. - if len(srcInfo.RebaseName) != 0 { - srcBase = srcInfo.RebaseName - } - return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil - } -} - -// RebaseArchiveEntries rewrites the given srcContent archive replacing -// an occurrence of oldBase with newBase at the beginning of entry names. -func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.ReadCloser { - if oldBase == string(os.PathSeparator) { - // If oldBase specifies the root directory, use an empty string as - // oldBase instead so that newBase doesn't replace the path separator - // that all paths will start with. - oldBase = "" - } - - rebased, w := io.Pipe() - - go func() { - srcTar := tar.NewReader(srcContent) - rebasedTar := tar.NewWriter(w) - - for { - hdr, err := srcTar.Next() - if errors.Is(err, io.EOF) { - // Signals end of archive. - rebasedTar.Close() - w.Close() - return - } - if err != nil { - w.CloseWithError(err) - return - } - - // srcContent tar stream, as served by TarWithOptions(), is - // definitely in PAX format, but tar.Next() mistakenly guesses it - // as USTAR, which creates a problem: if the newBase is >100 - // characters long, WriteHeader() returns an error like - // "archive/tar: cannot encode header: Format specifies USTAR; and USTAR cannot encode Name=...". - // - // To fix, set the format to PAX here. See docker/for-linux issue #484. - hdr.Format = tar.FormatPAX - hdr.Name = strings.Replace(hdr.Name, oldBase, newBase, 1) - if hdr.Typeflag == tar.TypeLink { - hdr.Linkname = strings.Replace(hdr.Linkname, oldBase, newBase, 1) - } - - if err = rebasedTar.WriteHeader(hdr); err != nil { - w.CloseWithError(err) - return - } - - // Ignoring GoSec G110. See https://github.com/securego/gosec/pull/433 - // and https://cure53.de/pentest-report_opa.pdf, which recommends to - // replace io.Copy with io.CopyN7. The latter allows to specify the - // maximum number of bytes that should be read. By properly defining - // the limit, it can be assured that a GZip compression bomb cannot - // easily cause a Denial-of-Service. - // After reviewing with @tonistiigi and @cpuguy83, this should not - // affect us, because here we do not read into memory, hence should - // not be vulnerable to this code consuming memory. - //nolint:gosec // G110: Potential DoS vulnerability via decompression bomb (gosec) - if _, err = io.Copy(rebasedTar, srcTar); err != nil { - w.CloseWithError(err) - return - } - } - }() - - return rebased -} - -// CopyResource performs an archive copy from the given source path to the -// given destination path. The source path MUST exist and the destination -// path's parent directory must exist. -func CopyResource(srcPath, dstPath string, followLink bool) error { - var ( - srcInfo CopyInfo - err error - ) - - // Ensure in platform semantics - srcPath = normalizePath(srcPath) - dstPath = normalizePath(dstPath) - - // Clean the source and destination paths. - srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath) - dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath) - - if srcInfo, err = CopyInfoSourcePath(srcPath, followLink); err != nil { - return err - } - - content, err := TarResource(srcInfo) - if err != nil { - return err - } - defer content.Close() - - return CopyTo(content, srcInfo, dstPath) -} - -// CopyTo handles extracting the given content whose -// entries should be sourced from srcInfo to dstPath. -func CopyTo(content io.Reader, srcInfo CopyInfo, dstPath string) error { - // The destination path need not exist, but CopyInfoDestinationPath will - // ensure that at least the parent directory exists. - dstInfo, err := CopyInfoDestinationPath(normalizePath(dstPath)) - if err != nil { - return err - } - - dstDir, copyArchive, err := PrepareArchiveCopy(content, srcInfo, dstInfo) - if err != nil { - return err - } - defer copyArchive.Close() - - options := &TarOptions{ - NoLchown: true, - NoOverwriteDirNonDir: true, - } - - return Untar(copyArchive, dstDir, options) -} - -// ResolveHostSourcePath decides real path need to be copied with parameters such as -// whether to follow symbol link or not, if followLink is true, resolvedPath will return -// link target of any symbol link file, else it will only resolve symlink of directory -// but return symbol link file itself without resolving. -func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseName string, _ error) { - if followLink { - var err error - resolvedPath, err = filepath.EvalSymlinks(path) - if err != nil { - return "", "", err - } - - resolvedPath, rebaseName = GetRebaseName(path, resolvedPath) - } else { - dirPath, basePath := filepath.Split(path) - - // if not follow symbol link, then resolve symbol link of parent dir - resolvedDirPath, err := filepath.EvalSymlinks(dirPath) - if err != nil { - return "", "", err - } - // resolvedDirPath will have been cleaned (no trailing path separators) so - // we can manually join it with the base path element. - resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath - if hasTrailingPathSeparator(path) && - filepath.Base(path) != filepath.Base(resolvedPath) { - rebaseName = filepath.Base(path) - } - } - return resolvedPath, rebaseName, nil -} - -// GetRebaseName normalizes and compares path and resolvedPath, -// return completed resolved path and rebased file name -func GetRebaseName(path, resolvedPath string) (string, string) { - // linkTarget will have been cleaned (no trailing path separators and dot) so - // we can manually join it with them - var rebaseName string - if specifiesCurrentDir(path) && - !specifiesCurrentDir(resolvedPath) { - resolvedPath += string(filepath.Separator) + "." - } - - if hasTrailingPathSeparator(path) && - !hasTrailingPathSeparator(resolvedPath) { - resolvedPath += string(filepath.Separator) - } - - if filepath.Base(path) != filepath.Base(resolvedPath) { - // In the case where the path had a trailing separator and a symlink - // evaluation has changed the last path component, we will need to - // rebase the name in the archive that is being copied to match the - // originally requested name. - rebaseName = filepath.Base(path) - } - return resolvedPath, rebaseName -} diff --git a/vendor/github.com/moby/go-archive/copy_unix.go b/vendor/github.com/moby/go-archive/copy_unix.go deleted file mode 100644 index f579282449af..000000000000 --- a/vendor/github.com/moby/go-archive/copy_unix.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !windows - -package archive - -import ( - "path/filepath" -) - -func normalizePath(path string) string { - return filepath.ToSlash(path) -} diff --git a/vendor/github.com/moby/go-archive/copy_windows.go b/vendor/github.com/moby/go-archive/copy_windows.go deleted file mode 100644 index 2b775b45c4f1..000000000000 --- a/vendor/github.com/moby/go-archive/copy_windows.go +++ /dev/null @@ -1,9 +0,0 @@ -package archive - -import ( - "path/filepath" -) - -func normalizePath(path string) string { - return filepath.FromSlash(path) -} diff --git a/vendor/github.com/moby/go-archive/dev_freebsd.go b/vendor/github.com/moby/go-archive/dev_freebsd.go deleted file mode 100644 index b3068fce9110..000000000000 --- a/vendor/github.com/moby/go-archive/dev_freebsd.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build freebsd - -package archive - -import "golang.org/x/sys/unix" - -func mknod(path string, mode uint32, dev uint64) error { - return unix.Mknod(path, mode, dev) -} diff --git a/vendor/github.com/moby/go-archive/dev_unix.go b/vendor/github.com/moby/go-archive/dev_unix.go deleted file mode 100644 index dffc596f93f7..000000000000 --- a/vendor/github.com/moby/go-archive/dev_unix.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !windows && !freebsd - -package archive - -import "golang.org/x/sys/unix" - -func mknod(path string, mode uint32, dev uint64) error { - return unix.Mknod(path, mode, int(dev)) -} diff --git a/vendor/github.com/moby/go-archive/diff.go b/vendor/github.com/moby/go-archive/diff.go deleted file mode 100644 index 96db972d1383..000000000000 --- a/vendor/github.com/moby/go-archive/diff.go +++ /dev/null @@ -1,261 +0,0 @@ -package archive - -import ( - "archive/tar" - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/containerd/log" - - "github.com/moby/go-archive/compression" -) - -// UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be -// compressed or uncompressed. -// Returns the size in bytes of the contents of the layer. -func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, err error) { - tr := tar.NewReader(layer) - - var dirs []*tar.Header - unpackedPaths := make(map[string]struct{}) - - if options == nil { - options = &TarOptions{} - } - if options.ExcludePatterns == nil { - options.ExcludePatterns = []string{} - } - - aufsTempdir := "" - aufsHardlinks := make(map[string]*tar.Header) - - // Iterate through the files in the archive. - for { - hdr, err := tr.Next() - if errors.Is(err, io.EOF) { - // end of tar archive - break - } - if err != nil { - return 0, err - } - - size += hdr.Size - - // Normalize name, for safety and for a simple is-root check - hdr.Name = filepath.Clean(hdr.Name) - - // Windows does not support filenames with colons in them. Ignore - // these files. This is not a problem though (although it might - // appear that it is). Let's suppose a client is running docker pull. - // The daemon it points to is Windows. Would it make sense for the - // client to be doing a docker pull Ubuntu for example (which has files - // with colons in the name under /usr/share/man/man3)? No, absolutely - // not as it would really only make sense that they were pulling a - // Windows image. However, for development, it is necessary to be able - // to pull Linux images which are in the repository. - // - // TODO Windows. Once the registry is aware of what images are Windows- - // specific or Linux-specific, this warning should be changed to an error - // to cater for the situation where someone does manage to upload a Linux - // image but have it tagged as Windows inadvertently. - if runtime.GOOS == "windows" { - if strings.Contains(hdr.Name, ":") { - log.G(context.TODO()).Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name) - continue - } - } - - // Ensure that the parent directory exists. - err = createImpliedDirectories(dest, hdr, options) - if err != nil { - return 0, err - } - - // Skip AUFS metadata dirs - if strings.HasPrefix(hdr.Name, WhiteoutMetaPrefix) { - // Regular files inside /.wh..wh.plnk can be used as hardlink targets - // We don't want this directory, but we need the files in them so that - // such hardlinks can be resolved. - if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg { - basename := filepath.Base(hdr.Name) - aufsHardlinks[basename] = hdr - if aufsTempdir == "" { - if aufsTempdir, err = os.MkdirTemp(dest, "dockerplnk"); err != nil { - return 0, err - } - defer os.RemoveAll(aufsTempdir) - } - if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, options); err != nil { - return 0, err - } - } - - if hdr.Name != WhiteoutOpaqueDir { - continue - } - } - // #nosec G305 -- The joined path is guarded against path traversal. - path := filepath.Join(dest, hdr.Name) - rel, err := filepath.Rel(dest, path) - if err != nil { - return 0, err - } - - // Note as these operations are platform specific, so must the slash be. - if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { - return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) - } - base := filepath.Base(path) - - if strings.HasPrefix(base, WhiteoutPrefix) { - dir := filepath.Dir(path) - if base == WhiteoutOpaqueDir { - _, err := os.Lstat(dir) - if err != nil { - return 0, err - } - err = filepath.WalkDir(dir, func(path string, info os.DirEntry, 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 { - return os.RemoveAll(path) - } - return nil - }) - if err != nil { - return 0, err - } - } else { - originalBase := base[len(WhiteoutPrefix):] - originalPath := filepath.Join(dir, originalBase) - if err := os.RemoveAll(originalPath); err != nil { - return 0, err - } - } - } else { - // If path exits we almost always just want to remove and replace it. - // The only exception is when it is a directory *and* the file from - // the layer is also a directory. Then we want to merge them (i.e. - // just apply the metadata from the layer). - if fi, err := os.Lstat(path); err == nil { - if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { - if err := os.RemoveAll(path); err != nil { - return 0, err - } - } - } - - 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, errors.New("invalid aufs hardlink") - } - tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename)) - if err != nil { - return 0, err - } - defer tmpFile.Close() - srcData = tmpFile - } - - if err := remapIDs(options.IDMap, srcHdr); err != nil { - return 0, err - } - - if err := createTarFile(path, dest, srcHdr, srcData, options); err != nil { - return 0, err - } - - // Directory mtimes must be handled at the end to avoid further - // file creation in them to modify the directory mtime - if hdr.Typeflag == tar.TypeDir { - dirs = append(dirs, hdr) - } - unpackedPaths[path] = struct{}{} - } - } - - for _, hdr := range dirs { - // #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice. - path := filepath.Join(dest, hdr.Name) - if err := chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil { - return 0, err - } - } - - return size, nil -} - -// ApplyLayer parses a diff in the standard layer format from `layer`, -// and applies it to the directory `dest`. The stream `layer` can be -// compressed or uncompressed. -// Returns the size in bytes of the contents of the layer. -func ApplyLayer(dest string, layer io.Reader) (int64, error) { - return applyLayerHandler(dest, layer, &TarOptions{}, true) -} - -// ApplyUncompressedLayer parses a diff in the standard layer format from -// `layer`, and applies it to the directory `dest`. The stream `layer` -// can only be uncompressed. -// Returns the size in bytes of the contents of the layer. -func ApplyUncompressedLayer(dest string, layer io.Reader, options *TarOptions) (int64, error) { - return applyLayerHandler(dest, layer, options, false) -} - -// IsEmpty checks if the tar archive is empty (doesn't contain any entries). -func IsEmpty(rd io.Reader) (bool, error) { - decompRd, err := compression.DecompressStream(rd) - if err != nil { - return true, fmt.Errorf("failed to decompress archive: %w", err) - } - defer decompRd.Close() - - tarReader := tar.NewReader(decompRd) - if _, err := tarReader.Next(); err != nil { - if errors.Is(err, io.EOF) { - return true, nil - } - return false, fmt.Errorf("failed to read next archive header: %w", err) - } - - return false, nil -} - -// do the bulk load of ApplyLayer, but allow for not calling DecompressStream -func applyLayerHandler(dest string, layer io.Reader, options *TarOptions, decompress bool) (int64, error) { - dest = filepath.Clean(dest) - - // We need to be able to set any perms - restore := overrideUmask(0) - defer restore() - - if decompress { - decompLayer, err := compression.DecompressStream(layer) - if err != nil { - return 0, err - } - defer decompLayer.Close() - layer = decompLayer - } - return UnpackLayer(dest, layer, options) -} diff --git a/vendor/github.com/moby/go-archive/diff_unix.go b/vendor/github.com/moby/go-archive/diff_unix.go deleted file mode 100644 index 7216f2f4f97c..000000000000 --- a/vendor/github.com/moby/go-archive/diff_unix.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build !windows - -package archive - -import "golang.org/x/sys/unix" - -// overrideUmask sets current process's file mode creation mask to newmask -// and returns a function to restore it. -// -// WARNING for readers stumbling upon this code. Changing umask in a multi- -// threaded environment isn't safe. Don't use this without understanding the -// risks, and don't export this function for others to use (we shouldn't even -// be using this ourself). -// -// FIXME(thaJeztah): we should get rid of these hacks if possible. -func overrideUmask(newMask int) func() { - oldMask := unix.Umask(newMask) - return func() { - unix.Umask(oldMask) - } -} diff --git a/vendor/github.com/moby/go-archive/diff_windows.go b/vendor/github.com/moby/go-archive/diff_windows.go deleted file mode 100644 index d28f5b2dfdd9..000000000000 --- a/vendor/github.com/moby/go-archive/diff_windows.go +++ /dev/null @@ -1,6 +0,0 @@ -package archive - -// overrideUmask is a no-op on windows. -func overrideUmask(newmask int) func() { - return func() {} -} diff --git a/vendor/github.com/moby/go-archive/internal/mounttree/switchroot_linux.go b/vendor/github.com/moby/go-archive/internal/mounttree/switchroot_linux.go deleted file mode 100644 index cc0f64da681d..000000000000 --- a/vendor/github.com/moby/go-archive/internal/mounttree/switchroot_linux.go +++ /dev/null @@ -1,94 +0,0 @@ -package mounttree - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/moby/sys/mount" - "github.com/moby/sys/mountinfo" - "golang.org/x/sys/unix" -) - -// SwitchRoot changes path to be the root of the mount tree and changes the -// current working directory to the new root. -// -// This function bind-mounts onto path; it is the caller's responsibility to set -// the desired propagation mode of path's parent mount beforehand to prevent -// unwanted propagation into different mount namespaces. -func SwitchRoot(path string) error { - if mounted, _ := mountinfo.Mounted(path); !mounted { - if err := mount.Mount(path, path, "bind", "rbind,rw"); err != nil { - return realChroot(path) - } - } - - // setup oldRoot for pivot_root - pivotDir, err := os.MkdirTemp(path, ".pivot_root") - if err != nil { - return fmt.Errorf("error setting up pivot dir: %w", err) - } - - var mounted bool - defer func() { - if mounted { - // make sure pivotDir is not mounted before we try to remove it - if errCleanup := unix.Unmount(pivotDir, unix.MNT_DETACH); errCleanup != nil { - if err == nil { - err = errCleanup - } - return - } - } - - errCleanup := os.Remove(pivotDir) - // pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful - // because we already cleaned it up on failed pivot_root - if errCleanup != nil && !os.IsNotExist(errCleanup) { - errCleanup = fmt.Errorf("error cleaning up after pivot: %w", errCleanup) - if err == nil { - err = errCleanup - } - } - }() - - if err := unix.PivotRoot(path, pivotDir); err != nil { - // If pivot fails, fall back to the normal chroot after cleaning up temp dir - if err := os.Remove(pivotDir); err != nil { - return fmt.Errorf("error cleaning up after failed pivot: %w", err) - } - return realChroot(path) - } - mounted = true - - // This is the new path for where the old root (prior to the pivot) has been moved to - // This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction - pivotDir = filepath.Join("/", filepath.Base(pivotDir)) - - if err := unix.Chdir("/"); err != nil { - return fmt.Errorf("error changing to new root: %w", err) - } - - // Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host - if err := unix.Mount("", pivotDir, "", unix.MS_PRIVATE|unix.MS_REC, ""); err != nil { - return fmt.Errorf("error making old root private after pivot: %w", err) - } - - // Now unmount the old root so it's no longer visible from the new root - if err := unix.Unmount(pivotDir, unix.MNT_DETACH); err != nil { - return fmt.Errorf("error while unmounting old root after pivot: %w", err) - } - mounted = false - - return nil -} - -func realChroot(path string) error { - if err := unix.Chroot(path); err != nil { - return fmt.Errorf("error after fallback to chroot: %w", err) - } - if err := unix.Chdir("/"); err != nil { - return fmt.Errorf("error changing to new root after chroot: %w", err) - } - return nil -} diff --git a/vendor/github.com/moby/go-archive/internal/unshare/unshare_linux.go b/vendor/github.com/moby/go-archive/internal/unshare/unshare_linux.go deleted file mode 100644 index 8491b3abc031..000000000000 --- a/vendor/github.com/moby/go-archive/internal/unshare/unshare_linux.go +++ /dev/null @@ -1,173 +0,0 @@ -package unshare - -import ( - "fmt" - "os" - "runtime" - - "golang.org/x/sys/unix" -) - -func init() { - // The startup thread of a process is special in a few different ways. - // Most pertinent to the discussion at hand, any per-thread kernel state - // reflected in the /proc/[pid]/ directory for a process is taken from - // the state of the startup thread. Same goes for /proc/self/; it shows - // the state of the current process' startup thread, no matter which - // thread the files are being opened from. For most programs this is a - // distinction without a difference as the kernel state, such as the - // mount namespace and current working directory, is shared among (and - // kept synchronized across) all threads of a process. But things start - // to break down once threads start unsharing and modifying parts of - // their kernel state. - // - // The Go runtime schedules goroutines to execute on the startup thread, - // same as any other. How this could be problematic is best illustrated - // with a concrete example. Consider what happens if a call to - // Go(unix.CLONE_NEWNS, ...) spawned a goroutine which gets scheduled - // onto the startup thread. The thread's mount namespace will be - // unshared and modified. The contents of the /proc/[pid]/mountinfo file - // will then describe the mount tree of the unshared namespace, not the - // namespace of any other thread. It will remain this way until the - // process exits. (The startup thread is special in another way: exiting - // it puts the process into a "non-waitable zombie" state. To avoid this - // fate, the Go runtime parks the thread instead of exiting if a - // goroutine returns while locked to the startup thread. More - // information can be found in the Go runtime sources: - // `go doc -u -src runtime.mexit`.) The github.com/moby/sys/mountinfo - // package reads from /proc/self/mountinfo, so will read the mount tree - // for the wrong namespace if the startup thread has had its mount - // namespace unshared! The /proc/thread-self/ directory, introduced in - // Linux 3.17, is one potential solution to this problem, but every - // package which opens files in /proc/self/ would need to be updated, - // and fallbacks to /proc/self/task/[tid]/ would be required to support - // older kernels. Overlooking any reference to /proc/self/ would - // manifest as stochastically-reproducible bugs, so this is far from an - // ideal solution. - // - // Reading from /proc/self/ would not be a problem if we could prevent - // the per-thread state of the startup thread from being modified - // nondeterministically in the first place. We can accomplish this - // simply by locking the main() function to the startup thread! Doing so - // excludes any other goroutine from being scheduled on the thread. - runtime.LockOSThread() -} - -// reversibleSetnsFlags maps the unshare(2) flags whose effects can be fully -// reversed using setns(2). The values are the basenames of the corresponding -// /proc/self/task/[tid]/ns/ magic symlinks to use to save and restore the -// state. -var reversibleSetnsFlags = map[int]string{ - unix.CLONE_NEWCGROUP: "cgroup", - unix.CLONE_NEWNET: "net", - unix.CLONE_NEWUTS: "uts", - unix.CLONE_NEWPID: "pid", - unix.CLONE_NEWTIME: "time", - - // The following CLONE_NEW* flags are not included because they imply - // another, irreversible flag when used with unshare(2). - // - unix.CLONE_NEWIPC: implies CLONE_SYSVMEM - // - unix.CLONE_NEWNS: implies CLONE_FS - // - unix.CLONE_NEWUSER: implies CLONE_FS since Linux 3.9 -} - -// Go calls the given functions in a new goroutine, locked to an OS thread, -// which has had the parts of its execution state disassociated from the rest of -// the current process using [unshare(2)]. It blocks until the new goroutine has -// started and setupfn has returned. fn is only called if setupfn returns nil. A -// nil setupfn or fn is equivalent to passing a no-op function. -// -// The disassociated execution state and any changes made to it are only visible -// to the goroutine which the functions are called in. Any other goroutines, -// including ones started from the function, will see the same execution state -// as the rest of the process. -// -// The acceptable flags are documented in the [unshare(2)] Linux man-page. -// The corresponding CLONE_* constants are defined in package [unix]. -// -// # Warning -// -// This function may terminate the thread which the new goroutine executed on -// after fn returns, which could cause subprocesses started with the -// [syscall.SysProcAttr] Pdeathsig field set to be signaled before process -// termination. Any subprocess started before this function is called may be -// affected, in addition to any subprocesses started inside setupfn or fn. -// There are more details at https://go.dev/issue/27505. -// -// [unshare(2)]: https://man7.org/linux/man-pages/man2/unshare.2.html -func Go(flags int, setupfn func() error, fn func()) error { - started := make(chan error) - - maskedFlags := flags - for f := range reversibleSetnsFlags { - maskedFlags &^= f - } - isReversible := maskedFlags == 0 - - go func() { - // Prepare to manipulate per-thread kernel state. - runtime.LockOSThread() - - // Not all changes to the execution state can be reverted. - // If an irreversible change to the execution state is made, our - // only recourse is to have the tampered thread terminated by - // returning from this function while the goroutine remains - // wired to the thread. The Go runtime will terminate the thread - // and replace it with a fresh one as needed. - - if isReversible { - defer func() { - if isReversible { - // All execution state has been restored without error. - // The thread is once again fungible. - runtime.UnlockOSThread() - } - }() - tid := unix.Gettid() - for f, ns := range reversibleSetnsFlags { - if flags&f != f { - continue - } - // The /proc/thread-self directory was added in Linux 3.17. - // We are not using it to maximize compatibility. - pth := fmt.Sprintf("/proc/self/task/%d/ns/%s", tid, ns) - fd, err := unix.Open(pth, unix.O_RDONLY|unix.O_CLOEXEC, 0) - if err != nil { - started <- &os.PathError{Op: "open", Path: pth, Err: err} - return - } - defer func() { - if isReversible { - if err := unix.Setns(fd, 0); err != nil { - isReversible = false - } - } - _ = unix.Close(fd) - }() - } - } - - // Threads are implemented under Linux as processes which share - // a virtual memory space. Therefore in a multithreaded process - // unshare(2) disassociates parts of the calling thread's - // context from the thread it was clone(2)'d from. - if err := unix.Unshare(flags); err != nil { - started <- os.NewSyscallError("unshare", err) - return - } - - if setupfn != nil { - if err := setupfn(); err != nil { - started <- err - return - } - } - close(started) - - if fn != nil { - fn() - } - }() - - return <-started -} diff --git a/vendor/github.com/moby/go-archive/path.go b/vendor/github.com/moby/go-archive/path.go deleted file mode 100644 index 888a697581d2..000000000000 --- a/vendor/github.com/moby/go-archive/path.go +++ /dev/null @@ -1,20 +0,0 @@ -package archive - -// CheckSystemDriveAndRemoveDriveLetter verifies that a path, if it includes a drive letter, -// is the system drive. -// On Linux: this is a no-op. -// On Windows: this does the following> -// CheckSystemDriveAndRemoveDriveLetter verifies and manipulates a Windows path. -// This is used, for example, when validating a user provided path in docker cp. -// If a drive letter is supplied, it must be the system drive. The drive letter -// is always removed. Also, it translates it to OS semantics (IOW / to \). We -// need the path in this syntax so that it can ultimately be concatenated with -// a Windows long-path which doesn't support drive-letters. Examples: -// C: --> Fail -// C:\ --> \ -// a --> a -// /a --> \a -// d:\ --> Fail -func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) { - return checkSystemDriveAndRemoveDriveLetter(path) -} diff --git a/vendor/github.com/moby/go-archive/path_unix.go b/vendor/github.com/moby/go-archive/path_unix.go deleted file mode 100644 index 390264bf8580..000000000000 --- a/vendor/github.com/moby/go-archive/path_unix.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !windows - -package archive - -// checkSystemDriveAndRemoveDriveLetter is the non-Windows implementation -// of CheckSystemDriveAndRemoveDriveLetter -func checkSystemDriveAndRemoveDriveLetter(path string) (string, error) { - return path, nil -} diff --git a/vendor/github.com/moby/go-archive/path_windows.go b/vendor/github.com/moby/go-archive/path_windows.go deleted file mode 100644 index 7e18c8e4493d..000000000000 --- a/vendor/github.com/moby/go-archive/path_windows.go +++ /dev/null @@ -1,22 +0,0 @@ -package archive - -import ( - "fmt" - "path/filepath" - "strings" -) - -// checkSystemDriveAndRemoveDriveLetter is the Windows implementation -// of CheckSystemDriveAndRemoveDriveLetter -func checkSystemDriveAndRemoveDriveLetter(path string) (string, error) { - if len(path) == 2 && string(path[1]) == ":" { - return "", fmt.Errorf("no relative path specified in %q", path) - } - if !filepath.IsAbs(path) || len(path) < 2 { - return filepath.FromSlash(path), nil - } - if string(path[1]) == ":" && !strings.EqualFold(string(path[0]), "c") { - return "", fmt.Errorf("the specified path is not on the system drive (C:)") - } - return filepath.FromSlash(path[2:]), nil -} diff --git a/vendor/github.com/moby/go-archive/tarheader/tarheader.go b/vendor/github.com/moby/go-archive/tarheader/tarheader.go deleted file mode 100644 index 03732a4f844b..000000000000 --- a/vendor/github.com/moby/go-archive/tarheader/tarheader.go +++ /dev/null @@ -1,67 +0,0 @@ -package tarheader - -import ( - "archive/tar" - "os" -) - -// assert that we implement [tar.FileInfoNames]. -var _ tar.FileInfoNames = (*nosysFileInfo)(nil) - -// nosysFileInfo hides the system-dependent info of the wrapped FileInfo to -// prevent tar.FileInfoHeader from introspecting it and potentially calling into -// glibc. -// -// It implements [tar.FileInfoNames] to further prevent [tar.FileInfoHeader] -// from performing any lookups on go1.23 and up. see https://go.dev/issue/50102 -type nosysFileInfo struct { - os.FileInfo -} - -// Uname stubs out looking up username. It implements [tar.FileInfoNames] -// to prevent [tar.FileInfoHeader] from loading libraries to perform -// username lookups. -func (fi nosysFileInfo) Uname() (string, error) { - return "", nil -} - -// Gname stubs out looking up group-name. It implements [tar.FileInfoNames] -// to prevent [tar.FileInfoHeader] from loading libraries to perform -// username lookups. -func (fi nosysFileInfo) Gname() (string, error) { - return "", nil -} - -func (fi nosysFileInfo) Sys() interface{} { - // A Sys value of type *tar.Header is safe as it is system-independent. - // The tar.FileInfoHeader function copies the fields into the returned - // header without performing any OS lookups. - if sys, ok := fi.FileInfo.Sys().(*tar.Header); ok { - return sys - } - return nil -} - -// FileInfoHeaderNoLookups creates a partially-populated tar.Header from fi. -// -// Compared to the archive/tar.FileInfoHeader function, this function is safe to -// call from a chrooted process as it does not populate fields which would -// require operating system lookups. It behaves identically to -// tar.FileInfoHeader when fi is a FileInfo value returned from -// tar.Header.FileInfo(). -// -// When fi is a FileInfo for a native file, such as returned from os.Stat() and -// os.Lstat(), the returned Header value differs from one returned from -// tar.FileInfoHeader in the following ways. The Uname and Gname fields are not -// set as OS lookups would be required to populate them. The AccessTime and -// ChangeTime fields are not currently set (not yet implemented) although that -// is subject to change. Callers which require the AccessTime or ChangeTime -// fields to be zeroed should explicitly zero them out in the returned Header -// value to avoid any compatibility issues in the future. -func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) { - hdr, err := tar.FileInfoHeader(nosysFileInfo{fi}, link) - if err != nil { - return nil, err - } - return hdr, sysStat(fi, hdr) -} diff --git a/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go b/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go deleted file mode 100644 index 9c3311c63abc..000000000000 --- a/vendor/github.com/moby/go-archive/tarheader/tarheader_unix.go +++ /dev/null @@ -1,46 +0,0 @@ -//go:build !windows - -package tarheader - -import ( - "archive/tar" - "os" - "runtime" - "syscall" - - "golang.org/x/sys/unix" -) - -// sysStat populates hdr from system-dependent fields of fi without performing -// any OS lookups. -func sysStat(fi os.FileInfo, hdr *tar.Header) error { - // Devmajor and Devminor are only needed for special devices. - - // In FreeBSD, RDev for regular files is -1 (unless overridden by FS): - // https://cgit.freebsd.org/src/tree/sys/kern/vfs_default.c?h=stable/13#n1531 - // (NODEV is -1: https://cgit.freebsd.org/src/tree/sys/sys/param.h?h=stable/13#n241). - - // ZFS in particular does not override the default: - // https://cgit.freebsd.org/src/tree/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c?h=stable/13#n2027 - - // Since `Stat_t.Rdev` is uint64, the cast turns -1 into (2^64 - 1). - // Such large values cannot be encoded in a tar header. - if runtime.GOOS == "freebsd" && hdr.Typeflag != tar.TypeBlock && hdr.Typeflag != tar.TypeChar { - return nil - } - s, ok := fi.Sys().(*syscall.Stat_t) - if !ok { - return nil - } - - hdr.Uid = int(s.Uid) - hdr.Gid = int(s.Gid) - - if s.Mode&unix.S_IFBLK != 0 || - s.Mode&unix.S_IFCHR != 0 { - hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert - hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert - } - - return nil -} diff --git a/vendor/github.com/moby/go-archive/tarheader/tarheader_windows.go b/vendor/github.com/moby/go-archive/tarheader/tarheader_windows.go deleted file mode 100644 index 5d4483ce2618..000000000000 --- a/vendor/github.com/moby/go-archive/tarheader/tarheader_windows.go +++ /dev/null @@ -1,12 +0,0 @@ -package tarheader - -import ( - "archive/tar" - "os" -) - -// sysStat populates hdr from system-dependent fields of fi without performing -// any OS lookups. It is a no-op on Windows. -func sysStat(os.FileInfo, *tar.Header) error { - return nil -} diff --git a/vendor/github.com/moby/go-archive/time.go b/vendor/github.com/moby/go-archive/time.go deleted file mode 100644 index 4e9ae9508432..000000000000 --- a/vendor/github.com/moby/go-archive/time.go +++ /dev/null @@ -1,38 +0,0 @@ -package archive - -import ( - "syscall" - "time" - "unsafe" -) - -var ( - minTime = time.Unix(0, 0) - maxTime time.Time -) - -func init() { - if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 { - // This is a 64 bit timespec - // os.Chtimes limits time to the following - maxTime = time.Unix(0, 1<<63-1) - } else { - // This is a 32 bit timespec - maxTime = time.Unix(1<<31-1, 0) - } -} - -func boundTime(t time.Time) time.Time { - if t.Before(minTime) || t.After(maxTime) { - return minTime - } - - return t -} - -func latestTime(t1, t2 time.Time) time.Time { - if t1.Before(t2) { - return t2 - } - return t1 -} diff --git a/vendor/github.com/moby/go-archive/time_nonwindows.go b/vendor/github.com/moby/go-archive/time_nonwindows.go deleted file mode 100644 index 5bfdfa2f17e8..000000000000 --- a/vendor/github.com/moby/go-archive/time_nonwindows.go +++ /dev/null @@ -1,41 +0,0 @@ -//go:build !windows - -package archive - -import ( - "os" - "time" - - "golang.org/x/sys/unix" -) - -// chtimes changes the access time and modified time of a file at the given path. -// If the modified time is prior to the Unix Epoch (unixMinTime), or after the -// end of Unix Time (unixEpochTime), os.Chtimes has undefined behavior. In this -// case, Chtimes defaults to Unix Epoch, just in case. -func chtimes(name string, atime time.Time, mtime time.Time) error { - return os.Chtimes(name, atime, mtime) -} - -func timeToTimespec(time time.Time) unix.Timespec { - if time.IsZero() { - // Return UTIME_OMIT special value - return unix.Timespec{ - Sec: 0, - Nsec: (1 << 30) - 2, - } - } - return unix.NsecToTimespec(time.UnixNano()) -} - -func lchtimes(name string, atime time.Time, mtime time.Time) error { - utimes := [2]unix.Timespec{ - timeToTimespec(atime), - timeToTimespec(mtime), - } - err := unix.UtimesNanoAt(unix.AT_FDCWD, name, utimes[0:], unix.AT_SYMLINK_NOFOLLOW) - if err != nil && err != unix.ENOSYS { - return err - } - return err -} diff --git a/vendor/github.com/moby/go-archive/time_windows.go b/vendor/github.com/moby/go-archive/time_windows.go deleted file mode 100644 index af1f7c8f3a07..000000000000 --- a/vendor/github.com/moby/go-archive/time_windows.go +++ /dev/null @@ -1,32 +0,0 @@ -package archive - -import ( - "os" - "time" - - "golang.org/x/sys/windows" -) - -func chtimes(name string, atime time.Time, mtime time.Time) error { - if err := os.Chtimes(name, atime, mtime); err != nil { - return err - } - - pathp, err := windows.UTF16PtrFromString(name) - if err != nil { - return err - } - h, err := windows.CreateFile(pathp, - windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil, - windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0) - if err != nil { - return err - } - defer windows.Close(h) - c := windows.NsecToFiletime(mtime.UnixNano()) - return windows.SetFileTime(h, &c, nil, nil) -} - -func lchtimes(name string, atime time.Time, mtime time.Time) error { - return nil -} diff --git a/vendor/github.com/moby/go-archive/whiteouts.go b/vendor/github.com/moby/go-archive/whiteouts.go deleted file mode 100644 index d20478a10dc1..000000000000 --- a/vendor/github.com/moby/go-archive/whiteouts.go +++ /dev/null @@ -1,23 +0,0 @@ -package archive - -// Whiteouts are files with a special meaning for the layered filesystem. -// Docker uses AUFS whiteout files inside exported archives. In other -// filesystems these files are generated/handled on tar creation/extraction. - -// WhiteoutPrefix prefix means file is a whiteout. If this is followed by a -// filename this means that file has been removed from the base layer. -const WhiteoutPrefix = ".wh." - -// WhiteoutMetaPrefix prefix means whiteout has a special meaning and is not -// for removing an actual file. Normally these files are excluded from exported -// archives. -const 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. -const WhiteoutLinkDir = WhiteoutMetaPrefix + "plnk" - -// WhiteoutOpaqueDir file means directory has been made opaque - meaning -// readdir calls to this directory do not follow to lower layers. -const WhiteoutOpaqueDir = WhiteoutMetaPrefix + ".opq" diff --git a/vendor/github.com/moby/go-archive/wrap.go b/vendor/github.com/moby/go-archive/wrap.go deleted file mode 100644 index f8a97254eede..000000000000 --- a/vendor/github.com/moby/go-archive/wrap.go +++ /dev/null @@ -1,59 +0,0 @@ -package archive - -import ( - "archive/tar" - "bytes" - "io" -) - -// Generate generates a new archive from the content provided -// as input. -// -// `files` is a sequence of path/content pairs. A new file is -// added to the archive for each pair. -// If the last pair is incomplete, the file is created with an -// empty content. For example: -// -// Generate("foo.txt", "hello world", "emptyfile") -// -// The above call will return an archive with 2 files: -// - ./foo.txt with content "hello world" -// - ./empty with empty content -// -// FIXME: stream content instead of buffering -// FIXME: specify permissions and other archive metadata -func Generate(input ...string) (io.Reader, error) { - files := parseStringPairs(input...) - buf := new(bytes.Buffer) - tw := tar.NewWriter(buf) - for _, file := range files { - name, content := file[0], file[1] - hdr := &tar.Header{ - Name: name, - Size: int64(len(content)), - } - if err := tw.WriteHeader(hdr); err != nil { - return nil, err - } - if _, err := tw.Write([]byte(content)); err != nil { - return nil, err - } - } - if err := tw.Close(); err != nil { - return nil, err - } - return buf, nil -} - -func parseStringPairs(input ...string) [][2]string { - output := make([][2]string, 0, len(input)/2+1) - for i := 0; i < len(input); i += 2 { - var pair [2]string - pair[0] = input[i] - if i+1 < len(input) { - pair[1] = input[i+1] - } - output = append(output, pair) - } - return output -} diff --git a/vendor/github.com/moby/go-archive/xattr_supported.go b/vendor/github.com/moby/go-archive/xattr_supported.go deleted file mode 100644 index 652a1f0f349d..000000000000 --- a/vendor/github.com/moby/go-archive/xattr_supported.go +++ /dev/null @@ -1,52 +0,0 @@ -//go:build linux || darwin || freebsd || netbsd - -package archive - -import ( - "errors" - "fmt" - "io/fs" - - "golang.org/x/sys/unix" -) - -// lgetxattr retrieves the value of the extended attribute identified by attr -// and associated with the given path in the file system. -// It returns a nil slice and nil error if the xattr is not set. -func lgetxattr(path string, attr string) ([]byte, error) { - // Start with a 128 length byte array - dest := make([]byte, 128) - sz, err := unix.Lgetxattr(path, attr, dest) - - for errors.Is(err, unix.ERANGE) { - // Buffer too small, use zero-sized buffer to get the actual size - sz, err = unix.Lgetxattr(path, attr, []byte{}) - if err != nil { - return nil, wrapPathError("lgetxattr", path, attr, err) - } - dest = make([]byte, sz) - sz, err = unix.Lgetxattr(path, attr, dest) - } - - if err != nil { - if errors.Is(err, noattr) { - return nil, nil - } - return nil, wrapPathError("lgetxattr", path, attr, err) - } - - return dest[:sz], nil -} - -// lsetxattr sets the value of the extended attribute identified by attr -// and associated with the given path in the file system. -func lsetxattr(path string, attr string, data []byte, flags int) error { - return wrapPathError("lsetxattr", path, attr, unix.Lsetxattr(path, attr, data, flags)) -} - -func wrapPathError(op, path, attr string, err error) error { - if err == nil { - return nil - } - return &fs.PathError{Op: op, Path: path, Err: fmt.Errorf("xattr %q: %w", attr, err)} -} diff --git a/vendor/github.com/moby/go-archive/xattr_supported_linux.go b/vendor/github.com/moby/go-archive/xattr_supported_linux.go deleted file mode 100644 index f2e76465ae56..000000000000 --- a/vendor/github.com/moby/go-archive/xattr_supported_linux.go +++ /dev/null @@ -1,5 +0,0 @@ -package archive - -import "golang.org/x/sys/unix" - -var noattr = unix.ENODATA diff --git a/vendor/github.com/moby/go-archive/xattr_supported_unix.go b/vendor/github.com/moby/go-archive/xattr_supported_unix.go deleted file mode 100644 index 58a03e4a6371..000000000000 --- a/vendor/github.com/moby/go-archive/xattr_supported_unix.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build darwin || freebsd || netbsd - -package archive - -import "golang.org/x/sys/unix" - -var noattr = unix.ENOATTR diff --git a/vendor/github.com/moby/go-archive/xattr_unsupported.go b/vendor/github.com/moby/go-archive/xattr_unsupported.go deleted file mode 100644 index b0d9165cd94f..000000000000 --- a/vendor/github.com/moby/go-archive/xattr_unsupported.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !linux && !darwin && !freebsd && !netbsd - -package archive - -func lgetxattr(path string, attr string) ([]byte, error) { - return nil, nil -} - -func lsetxattr(path string, attr string, data []byte, flags int) error { - return nil -} diff --git a/vendor/github.com/moby/sys/mount/LICENSE b/vendor/github.com/moby/sys/mount/LICENSE deleted file mode 100644 index d64569567334..000000000000 --- a/vendor/github.com/moby/sys/mount/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/vendor/github.com/moby/sys/mount/doc.go b/vendor/github.com/moby/sys/mount/doc.go deleted file mode 100644 index 86c2e01bbd8c..000000000000 --- a/vendor/github.com/moby/sys/mount/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Package mount provides a set of functions to mount and unmount mounts. -// -// Currently it supports Linux. For historical reasons, there is also some support for FreeBSD. -package mount diff --git a/vendor/github.com/moby/sys/mount/flags_bsd.go b/vendor/github.com/moby/sys/mount/flags_bsd.go deleted file mode 100644 index e90f929decc1..000000000000 --- a/vendor/github.com/moby/sys/mount/flags_bsd.go +++ /dev/null @@ -1,45 +0,0 @@ -//go:build darwin || freebsd || openbsd - -package mount - -import "golang.org/x/sys/unix" - -const ( - // RDONLY will mount the filesystem as read-only. - RDONLY = unix.MNT_RDONLY - - // NOSUID will not allow set-user-identifier or set-group-identifier bits to - // take effect. - NOSUID = unix.MNT_NOSUID - - // NOEXEC will not allow execution of any binaries on the mounted file system. - NOEXEC = unix.MNT_NOEXEC - - // SYNCHRONOUS will allow any I/O to the file system to be done synchronously. - SYNCHRONOUS = unix.MNT_SYNCHRONOUS - - // NOATIME will not update the file access time when reading from a file. - NOATIME = unix.MNT_NOATIME -) - -// These flags are unsupported. -const ( - BIND = 0 - DIRSYNC = 0 - MANDLOCK = 0 - NODEV = 0 - NODIRATIME = 0 - UNBINDABLE = 0 - RUNBINDABLE = 0 - PRIVATE = 0 - RPRIVATE = 0 - SHARED = 0 - RSHARED = 0 - SLAVE = 0 - RSLAVE = 0 - RBIND = 0 - RELATIME = 0 - REMOUNT = 0 - STRICTATIME = 0 - mntDetach = 0 -) diff --git a/vendor/github.com/moby/sys/mount/flags_linux.go b/vendor/github.com/moby/sys/mount/flags_linux.go deleted file mode 100644 index 0425d0dd633c..000000000000 --- a/vendor/github.com/moby/sys/mount/flags_linux.go +++ /dev/null @@ -1,87 +0,0 @@ -package mount - -import ( - "golang.org/x/sys/unix" -) - -const ( - // RDONLY will mount the file system read-only. - RDONLY = unix.MS_RDONLY - - // NOSUID will not allow set-user-identifier or set-group-identifier bits to - // take effect. - NOSUID = unix.MS_NOSUID - - // NODEV will not interpret character or block special devices on the file - // system. - NODEV = unix.MS_NODEV - - // NOEXEC will not allow execution of any binaries on the mounted file system. - NOEXEC = unix.MS_NOEXEC - - // SYNCHRONOUS will allow I/O to the file system to be done synchronously. - SYNCHRONOUS = unix.MS_SYNCHRONOUS - - // DIRSYNC will force all directory updates within the file system to be done - // synchronously. This affects the following system calls: create, link, - // unlink, symlink, mkdir, rmdir, mknod and rename. - DIRSYNC = unix.MS_DIRSYNC - - // REMOUNT will attempt to remount an already-mounted file system. This is - // commonly used to change the mount flags for a file system, especially to - // make a readonly file system writeable. It does not change device or mount - // point. - REMOUNT = unix.MS_REMOUNT - - // MANDLOCK will force mandatory locks on a filesystem. - MANDLOCK = unix.MS_MANDLOCK - - // NOATIME will not update the file access time when reading from a file. - NOATIME = unix.MS_NOATIME - - // NODIRATIME will not update the directory access time. - NODIRATIME = unix.MS_NODIRATIME - - // BIND remounts a subtree somewhere else. - BIND = unix.MS_BIND - - // RBIND remounts a subtree and all possible submounts somewhere else. - RBIND = unix.MS_BIND | unix.MS_REC - - // UNBINDABLE creates a mount which cannot be cloned through a bind operation. - UNBINDABLE = unix.MS_UNBINDABLE - - // RUNBINDABLE marks the entire mount tree as UNBINDABLE. - RUNBINDABLE = unix.MS_UNBINDABLE | unix.MS_REC - - // PRIVATE creates a mount which carries no propagation abilities. - PRIVATE = unix.MS_PRIVATE - - // RPRIVATE marks the entire mount tree as PRIVATE. - RPRIVATE = unix.MS_PRIVATE | unix.MS_REC - - // SLAVE creates a mount which receives propagation from its master, but not - // vice versa. - SLAVE = unix.MS_SLAVE - - // RSLAVE marks the entire mount tree as SLAVE. - RSLAVE = unix.MS_SLAVE | unix.MS_REC - - // SHARED creates a mount which provides the ability to create mirrors of - // that mount such that mounts and unmounts within any of the mirrors - // propagate to the other mirrors. - SHARED = unix.MS_SHARED - - // RSHARED marks the entire mount tree as SHARED. - RSHARED = unix.MS_SHARED | unix.MS_REC - - // RELATIME updates inode access times relative to modify or change time. - RELATIME = unix.MS_RELATIME - - // STRICTATIME allows to explicitly request full atime updates. This makes - // it possible for the kernel to default to relatime or noatime but still - // allow userspace to override it. - STRICTATIME = unix.MS_STRICTATIME - - mntDetach = unix.MNT_DETACH -) diff --git a/vendor/github.com/moby/sys/mount/flags_unix.go b/vendor/github.com/moby/sys/mount/flags_unix.go deleted file mode 100644 index cf547a0fefc5..000000000000 --- a/vendor/github.com/moby/sys/mount/flags_unix.go +++ /dev/null @@ -1,139 +0,0 @@ -//go:build !windows - -package mount - -import ( - "fmt" - "strings" -) - -var flags = map[string]struct { - clear bool - flag int -}{ - "defaults": {false, 0}, - "ro": {false, RDONLY}, - "rw": {true, RDONLY}, - "suid": {true, NOSUID}, - "nosuid": {false, NOSUID}, - "dev": {true, NODEV}, - "nodev": {false, NODEV}, - "exec": {true, NOEXEC}, - "noexec": {false, NOEXEC}, - "sync": {false, SYNCHRONOUS}, - "async": {true, SYNCHRONOUS}, - "dirsync": {false, DIRSYNC}, - "remount": {false, REMOUNT}, - "mand": {false, MANDLOCK}, - "nomand": {true, MANDLOCK}, - "atime": {true, NOATIME}, - "noatime": {false, NOATIME}, - "diratime": {true, NODIRATIME}, - "nodiratime": {false, NODIRATIME}, - "bind": {false, BIND}, - "rbind": {false, RBIND}, - "unbindable": {false, UNBINDABLE}, - "runbindable": {false, RUNBINDABLE}, - "private": {false, PRIVATE}, - "rprivate": {false, RPRIVATE}, - "shared": {false, SHARED}, - "rshared": {false, RSHARED}, - "slave": {false, SLAVE}, - "rslave": {false, RSLAVE}, - "relatime": {false, RELATIME}, - "norelatime": {true, RELATIME}, - "strictatime": {false, STRICTATIME}, - "nostrictatime": {true, STRICTATIME}, -} - -var validFlags = map[string]bool{ - "": true, - "size": true, - "mode": true, - "uid": true, - "gid": true, - "nr_inodes": true, - "nr_blocks": true, - "mpol": true, -} - -var propagationFlags = map[string]bool{ - "bind": true, - "rbind": true, - "unbindable": true, - "runbindable": true, - "private": true, - "rprivate": true, - "shared": true, - "rshared": true, - "slave": true, - "rslave": true, -} - -// MergeTmpfsOptions merge mount options to make sure there is no duplicate. -func MergeTmpfsOptions(options []string) ([]string, error) { - // We use collisions maps to remove duplicates. - // For flag, the key is the flag value (the key for propagation flag is -1) - // For data=value, the key is the data - flagCollisions := map[int]bool{} - dataCollisions := map[string]bool{} - - var newOptions []string - // We process in reverse order - for i := len(options) - 1; i >= 0; i-- { - option := options[i] - if option == "defaults" { - continue - } - if f, ok := flags[option]; ok && f.flag != 0 { - // There is only one propagation mode - key := f.flag - if propagationFlags[option] { - key = -1 - } - // Check to see if there is collision for flag - if !flagCollisions[key] { - // We prepend the option and add to collision map - newOptions = append([]string{option}, newOptions...) - flagCollisions[key] = true - } - continue - } - opt, _, ok := strings.Cut(option, "=") - if !ok || !validFlags[opt] { - return nil, fmt.Errorf("invalid tmpfs option %q", opt) - } - if !dataCollisions[opt] { - // We prepend the option and add to collision map - newOptions = append([]string{option}, newOptions...) - dataCollisions[opt] = true - } - } - - return newOptions, nil -} - -// Parse fstab type mount options into mount() flags -// and device specific data -func parseOptions(options string) (int, string) { - var ( - flag int - data []string - ) - - for _, o := range strings.Split(options, ",") { - // If the option does not exist in the flags table or the flag - // is not supported on the platform, - // then it is a data value for a specific fs type - if f, exists := flags[o]; exists && f.flag != 0 { - if f.clear { - flag &= ^f.flag - } else { - flag |= f.flag - } - } else { - data = append(data, o) - } - } - return flag, strings.Join(data, ",") -} diff --git a/vendor/github.com/moby/sys/mount/mount_errors.go b/vendor/github.com/moby/sys/mount/mount_errors.go deleted file mode 100644 index c08bc12cda32..000000000000 --- a/vendor/github.com/moby/sys/mount/mount_errors.go +++ /dev/null @@ -1,46 +0,0 @@ -//go:build !windows - -package mount - -import "strconv" - -// mountError records an error from mount or unmount operation -type mountError struct { - op string - source, target string - flags uintptr - data string - err error -} - -func (e *mountError) Error() string { - out := e.op + " " - - if e.source != "" { - out += e.source + ":" + e.target - } else { - out += e.target - } - - if e.flags != uintptr(0) { - out += ", flags: 0x" + strconv.FormatUint(uint64(e.flags), 16) - } - if e.data != "" { - out += ", data: " + e.data - } - - out += ": " + e.err.Error() - return out -} - -// Cause returns the underlying cause of the error. -// This is a convention used in github.com/pkg/errors -func (e *mountError) Cause() error { - return e.err -} - -// Unwrap returns the underlying error. -// This is a convention used in golang 1.13+ -func (e *mountError) Unwrap() error { - return e.err -} diff --git a/vendor/github.com/moby/sys/mount/mount_unix.go b/vendor/github.com/moby/sys/mount/mount_unix.go deleted file mode 100644 index 4abd4ca0ccf8..000000000000 --- a/vendor/github.com/moby/sys/mount/mount_unix.go +++ /dev/null @@ -1,88 +0,0 @@ -//go:build !windows - -package mount - -import ( - "fmt" - "sort" - - "github.com/moby/sys/mountinfo" - "golang.org/x/sys/unix" -) - -// Mount will mount filesystem according to the specified configuration. -// Options must be specified like the mount or fstab unix commands: -// "opt1=val1,opt2=val2". See flags.go for supported option flags. -func Mount(device, target, mType, options string) error { - flag, data := parseOptions(options) - return mount(device, target, mType, uintptr(flag), data) -} - -// Unmount lazily unmounts a filesystem on supported platforms, otherwise does -// a normal unmount. If target is not a mount point, no error is returned. -func Unmount(target string) error { - err := unix.Unmount(target, mntDetach) - if err == nil || err == unix.EINVAL { - // Ignore "not mounted" error here. Note the same error - // can be returned if flags are invalid, so this code - // assumes that the flags value is always correct. - return nil - } - - return &mountError{ - op: "umount", - target: target, - flags: uintptr(mntDetach), - err: err, - } -} - -// RecursiveUnmount unmounts the target and all mounts underneath, starting -// with the deepest mount first. The argument does not have to be a mount -// point itself. -func RecursiveUnmount(target string) error { - // Fast path, works if target is a mount point that can be unmounted. - // On Linux, mntDetach flag ensures a recursive unmount. For other - // platforms, if there are submounts, we'll get EBUSY (and fall back - // to the slow path). NOTE we do not ignore EINVAL here as target might - // not be a mount point itself (but there can be mounts underneath). - if err := unix.Unmount(target, mntDetach); err == nil { - return nil - } - - // Slow path: get all submounts, sort, unmount one by one. - mounts, err := mountinfo.GetMounts(mountinfo.PrefixFilter(target)) - if err != nil { - return err - } - - // Make the deepest mount be first - sort.Slice(mounts, func(i, j int) bool { - return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint) - }) - - var ( - suberr error - lastMount = len(mounts) - 1 - ) - for i, m := range mounts { - err = Unmount(m.Mountpoint) - if err != nil { - if i == lastMount { - if suberr != nil { - // TODO: switch to %w for suberr once we stop supporting go < 1.20. - return fmt.Errorf("%w (possible cause: %s)", err, suberr.Error()) - } - return err - } - // This is a submount, we can ignore the error for now, - // the final unmount will fail if this is a real problem. - // With that in mind, the _first_ failed unmount error - // might be the real error cause, so let's keep it. - if suberr == nil { - suberr = err - } - } - } - return nil -} diff --git a/vendor/github.com/moby/sys/mount/mounter_freebsd.go b/vendor/github.com/moby/sys/mount/mounter_freebsd.go deleted file mode 100644 index 14485069fa0e..000000000000 --- a/vendor/github.com/moby/sys/mount/mounter_freebsd.go +++ /dev/null @@ -1,61 +0,0 @@ -//go:build freebsd && cgo - -package mount - -/* -#include -#include -#include -#include -#include -#include -*/ -import "C" - -import ( - "strings" - "syscall" - "unsafe" -) - -func allocateIOVecs(options []string) []C.struct_iovec { - out := make([]C.struct_iovec, len(options)) - for i, option := range options { - out[i].iov_base = unsafe.Pointer(C.CString(option)) - out[i].iov_len = C.size_t(len(option) + 1) - } - return out -} - -func mount(device, target, mType string, flag uintptr, data string) error { - isNullFS := false - - xs := strings.Split(data, ",") - for _, x := range xs { - if x == "bind" { - isNullFS = true - } - } - - options := []string{"fspath", target} - if isNullFS { - options = append(options, "fstype", "nullfs", "target", device) - } else { - options = append(options, "fstype", mType, "from", device) - } - rawOptions := allocateIOVecs(options) - for _, rawOption := range rawOptions { - defer C.free(rawOption.iov_base) - } - - if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 { - return &mountError{ - op: "mount", - source: device, - target: target, - flags: flag, - err: syscall.Errno(errno), - } - } - return nil -} diff --git a/vendor/github.com/moby/sys/mount/mounter_linux.go b/vendor/github.com/moby/sys/mount/mounter_linux.go deleted file mode 100644 index 4e18f4b6786c..000000000000 --- a/vendor/github.com/moby/sys/mount/mounter_linux.go +++ /dev/null @@ -1,72 +0,0 @@ -package mount - -import ( - "golang.org/x/sys/unix" -) - -const ( - // ptypes is the set propagation types. - ptypes = unix.MS_SHARED | unix.MS_PRIVATE | unix.MS_SLAVE | unix.MS_UNBINDABLE - - // pflags is the full set valid flags for a change propagation call. - pflags = ptypes | unix.MS_REC | unix.MS_SILENT - - // broflags is the combination of bind and read only - broflags = unix.MS_BIND | unix.MS_RDONLY -) - -// isremount returns true if either device name or flags identify a remount request, false otherwise. -func isremount(device string, flags uintptr) bool { - switch { - // We treat device "" and "none" as a remount request to provide compatibility with - // requests that don't explicitly set MS_REMOUNT such as those manipulating bind mounts. - case flags&unix.MS_REMOUNT != 0, device == "", device == "none": - return true - default: - return false - } -} - -func mount(device, target, mType string, flags uintptr, data string) error { - oflags := flags &^ ptypes - if !isremount(device, flags) || data != "" { - // Initial call applying all non-propagation flags for mount - // or remount with changed data - if err := unix.Mount(device, target, mType, oflags, data); err != nil { - return &mountError{ - op: "mount", - source: device, - target: target, - flags: oflags, - data: data, - err: err, - } - } - } - - if flags&ptypes != 0 { - // Change the propagation type. - if err := unix.Mount("", target, "", flags&pflags, ""); err != nil { - return &mountError{ - op: "remount", - target: target, - flags: flags & pflags, - err: err, - } - } - } - - if oflags&broflags == broflags { - // Remount the bind to apply read only. - if err := unix.Mount("", target, "", oflags|unix.MS_REMOUNT, ""); err != nil { - return &mountError{ - op: "remount-ro", - target: target, - flags: oflags | unix.MS_REMOUNT, - err: err, - } - } - } - - return nil -} diff --git a/vendor/github.com/moby/sys/mount/mounter_openbsd.go b/vendor/github.com/moby/sys/mount/mounter_openbsd.go deleted file mode 100644 index 86a0b242a797..000000000000 --- a/vendor/github.com/moby/sys/mount/mounter_openbsd.go +++ /dev/null @@ -1,77 +0,0 @@ -//go:build openbsd && cgo - -/* - Due to how OpenBSD mount(2) works, filesystem types need to be - supported explicitly since it uses separate structs to pass - filesystem-specific arguments. - - For now only UFS/FFS is supported as it's the default fs - on OpenBSD systems. - - See: https://man.openbsd.org/mount.2 -*/ - -package mount - -/* -#include -#include -*/ -import "C" - -import ( - "fmt" - "syscall" - "unsafe" -) - -func createExportInfo(readOnly bool) C.struct_export_args { - exportFlags := C.int(0) - if readOnly { - exportFlags = C.MNT_EXRDONLY - } - out := C.struct_export_args{ - ex_root: 0, - ex_flags: exportFlags, - } - return out -} - -func createUfsArgs(device string, readOnly bool) unsafe.Pointer { - out := &C.struct_ufs_args{ - fspec: C.CString(device), - export_info: createExportInfo(readOnly), - } - return unsafe.Pointer(out) -} - -func mount(device, target, mType string, flag uintptr, data string) error { - readOnly := flag&RDONLY != 0 - - var fsArgs unsafe.Pointer - - switch mType { - case "ffs": - fsArgs = createUfsArgs(device, readOnly) - default: - return &mountError{ - op: "mount", - source: device, - target: target, - flags: flag, - err: fmt.Errorf("unsupported file system type: %s", mType), - } - } - - if errno := C.mount(C.CString(mType), C.CString(target), C.int(flag), fsArgs); errno != 0 { - return &mountError{ - op: "mount", - source: device, - target: target, - flags: flag, - err: syscall.Errno(errno), - } - } - - return nil -} diff --git a/vendor/github.com/moby/sys/mount/mounter_unsupported.go b/vendor/github.com/moby/sys/mount/mounter_unsupported.go deleted file mode 100644 index 17b8b650ec80..000000000000 --- a/vendor/github.com/moby/sys/mount/mounter_unsupported.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build (!linux && !freebsd && !openbsd && !windows) || (freebsd && !cgo) || (openbsd && !cgo) - -package mount - -func mount(device, target, mType string, flag uintptr, data string) error { - panic("cgo required on freebsd and openbsd") -} diff --git a/vendor/github.com/moby/sys/mount/sharedsubtree_linux.go b/vendor/github.com/moby/sys/mount/sharedsubtree_linux.go deleted file mode 100644 index 948e6bacd8ae..000000000000 --- a/vendor/github.com/moby/sys/mount/sharedsubtree_linux.go +++ /dev/null @@ -1,73 +0,0 @@ -package mount - -import "github.com/moby/sys/mountinfo" - -// MakeShared ensures a mounted filesystem has the SHARED mount option enabled. -// See the supported options in flags.go for further reference. -func MakeShared(mountPoint string) error { - return ensureMountedAs(mountPoint, SHARED) -} - -// MakeRShared ensures a mounted filesystem has the RSHARED mount option enabled. -// See the supported options in flags.go for further reference. -func MakeRShared(mountPoint string) error { - return ensureMountedAs(mountPoint, RSHARED) -} - -// MakePrivate ensures a mounted filesystem has the PRIVATE mount option enabled. -// See the supported options in flags.go for further reference. -func MakePrivate(mountPoint string) error { - return ensureMountedAs(mountPoint, PRIVATE) -} - -// MakeRPrivate ensures a mounted filesystem has the RPRIVATE mount option -// enabled. See the supported options in flags.go for further reference. -func MakeRPrivate(mountPoint string) error { - return ensureMountedAs(mountPoint, RPRIVATE) -} - -// MakeSlave ensures a mounted filesystem has the SLAVE mount option enabled. -// See the supported options in flags.go for further reference. -func MakeSlave(mountPoint string) error { - return ensureMountedAs(mountPoint, SLAVE) -} - -// MakeRSlave ensures a mounted filesystem has the RSLAVE mount option enabled. -// See the supported options in flags.go for further reference. -func MakeRSlave(mountPoint string) error { - return ensureMountedAs(mountPoint, RSLAVE) -} - -// MakeUnbindable ensures a mounted filesystem has the UNBINDABLE mount option -// enabled. See the supported options in flags.go for further reference. -func MakeUnbindable(mountPoint string) error { - return ensureMountedAs(mountPoint, UNBINDABLE) -} - -// MakeRUnbindable ensures a mounted filesystem has the RUNBINDABLE mount -// option enabled. See the supported options in flags.go for further reference. -func MakeRUnbindable(mountPoint string) error { - return ensureMountedAs(mountPoint, RUNBINDABLE) -} - -// MakeMount ensures that the file or directory given is a mount point, -// bind mounting it to itself it case it is not. -func MakeMount(mnt string) error { - mounted, err := mountinfo.Mounted(mnt) - if err != nil { - return err - } - if mounted { - return nil - } - - return mount(mnt, mnt, "none", uintptr(BIND), "") -} - -func ensureMountedAs(mnt string, flags int) error { - if err := MakeMount(mnt); err != nil { - return err - } - - return mount("", mnt, "none", uintptr(flags), "") -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 199b09c2959f..3927daaa99ed 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -795,14 +795,6 @@ github.com/kylelemons/godebug/pretty # github.com/moby/docker-image-spec v1.3.1 ## explicit; go 1.18 github.com/moby/docker-image-spec/specs-go/v1 -# github.com/moby/go-archive v0.2.0 -## explicit; go 1.23.0 -github.com/moby/go-archive -github.com/moby/go-archive/chrootarchive -github.com/moby/go-archive/compression -github.com/moby/go-archive/internal/mounttree -github.com/moby/go-archive/internal/unshare -github.com/moby/go-archive/tarheader # github.com/moby/locker v1.0.1 ## explicit; go 1.13 github.com/moby/locker @@ -823,9 +815,6 @@ github.com/moby/profiles/seccomp # github.com/moby/sys/capability v0.4.0 ## explicit; go 1.21 github.com/moby/sys/capability -# github.com/moby/sys/mount v0.3.5 -## explicit; go 1.18 -github.com/moby/sys/mount # github.com/moby/sys/mountinfo v0.7.2 ## explicit; go 1.17 github.com/moby/sys/mountinfo