vendor: update buildkit to v0.32.0-rc2
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
@@ -30,8 +30,8 @@ require (
|
|||||||
github.com/hashicorp/hcl/v2 v2.24.0
|
github.com/hashicorp/hcl/v2 v2.24.0
|
||||||
github.com/in-toto/in-toto-golang v0.11.0
|
github.com/in-toto/in-toto-golang v0.11.0
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
github.com/moby/buildkit v0.32.0-rc1
|
github.com/moby/buildkit v0.32.0-rc2
|
||||||
github.com/moby/go-archive v0.2.0
|
github.com/moby/go-archive v0.2.1
|
||||||
github.com/moby/moby/api v1.55.0
|
github.com/moby/moby/api v1.55.0
|
||||||
github.com/moby/moby/client v0.5.0
|
github.com/moby/moby/client v0.5.0
|
||||||
github.com/moby/policy-helpers v0.0.0-20260722051018-856be88baec4
|
github.com/moby/policy-helpers v0.0.0-20260722051018-856be88baec4
|
||||||
|
|||||||
@@ -395,12 +395,12 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4
|
|||||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/moby/buildkit v0.32.0-rc1 h1:gYtsqXOSA43i9zTf2w+0KzTxpZYao0ZYr/AaGx0RJhs=
|
github.com/moby/buildkit v0.32.0-rc2 h1:2XiJmnPSZyJ0akoKG2ShWAFbWRT5DKvyAI2+Johq9Ac=
|
||||||
github.com/moby/buildkit v0.32.0-rc1/go.mod h1:0GB/EJ1d+4VIVqIAgy3asaoGkVXy7IrDfVy7mPhOvg8=
|
github.com/moby/buildkit v0.32.0-rc2/go.mod h1:Y10FBWvqxl/Wmhdzjee1Y2wQfjifTiwxENIUdaVNdME=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
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/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.1 h1:fAa0wUS/ikZKyx7o/1fhUYmhZ7RgpthdeoDhJvunTLc=
|
||||||
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
|
github.com/moby/go-archive v0.2.1/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE=
|
||||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
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/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||||
github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
|
github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
|
||||||
|
|||||||
+12
@@ -9,6 +9,7 @@ issues:
|
|||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- errorlint
|
- errorlint
|
||||||
|
- gosec
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
exclusions:
|
exclusions:
|
||||||
@@ -16,7 +17,18 @@ linters:
|
|||||||
presets:
|
presets:
|
||||||
- comments
|
- comments
|
||||||
- std-error-handling
|
- std-error-handling
|
||||||
|
rules:
|
||||||
|
# Ignore "G204: Subprocess launched with a potential tainted input or cmd arguments"
|
||||||
|
- path: '(.+)_test\.go'
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
text: 'G204: Subprocess launched'
|
||||||
settings:
|
settings:
|
||||||
|
gosec:
|
||||||
|
excludes:
|
||||||
|
- G301 # Expect directory permissions to be 0750 or less
|
||||||
|
- G304 # Potential file inclusion via variable
|
||||||
|
- G306 # Expect WriteFile permissions to be 0600 or less
|
||||||
staticcheck:
|
staticcheck:
|
||||||
# Enable all options, with some exceptions.
|
# Enable all options, with some exceptions.
|
||||||
# For defaults, see https://golangci-lint.run/usage/linters/#staticcheck
|
# For defaults, see https://golangci-lint.run/usage/linters/#staticcheck
|
||||||
|
|||||||
+113
-95
@@ -46,9 +46,18 @@ type (
|
|||||||
|
|
||||||
// TarOptions wraps the tar options.
|
// TarOptions wraps the tar options.
|
||||||
TarOptions struct {
|
TarOptions struct {
|
||||||
|
// IncludeFiles lists archive-relative paths to include.
|
||||||
|
// Paths use POSIX ('/') separators.
|
||||||
IncludeFiles []string
|
IncludeFiles []string
|
||||||
|
|
||||||
|
// ExcludePatterns lists archive-relative exclude patterns.
|
||||||
|
// Patterns use POSIX ('/') separators, matching patternmatcher semantics.
|
||||||
ExcludePatterns []string
|
ExcludePatterns []string
|
||||||
Compression compression.Compression
|
Compression compression.Compression
|
||||||
|
// NoLchown disables applying ownership from the archive to extracted files
|
||||||
|
// and directories. Despite its historical name, it applies to all ownership
|
||||||
|
// changes, leaving extracted filesystem objects owned by the user performing
|
||||||
|
// the extraction.
|
||||||
NoLchown bool
|
NoLchown bool
|
||||||
IDMap user.IdentityMapping
|
IDMap user.IdentityMapping
|
||||||
ChownOpts *ChownOpts
|
ChownOpts *ChownOpts
|
||||||
@@ -86,10 +95,14 @@ func NewDefaultArchiver() *Archiver {
|
|||||||
return &Archiver{Untar: Untar}
|
return &Archiver{Untar: Untar}
|
||||||
}
|
}
|
||||||
|
|
||||||
// breakoutError is used to differentiate errors related to breaking out
|
// breakoutErr marks errors caused by archive breakout attempts.
|
||||||
// When testing archive breakout in the unit tests, this error is expected
|
// Unit tests use it to distinguish expected breakout failures from other
|
||||||
// in order for the test to pass.
|
// errors.
|
||||||
type breakoutError error
|
type breakoutErr struct{ error }
|
||||||
|
|
||||||
|
func breakoutError(err error) error {
|
||||||
|
return &breakoutErr{error: err}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AUFSWhiteoutFormat WhiteoutFormat = 0 // AUFSWhiteoutFormat is the default format for whiteouts
|
AUFSWhiteoutFormat WhiteoutFormat = 0 // AUFSWhiteoutFormat is the default format for whiteouts
|
||||||
@@ -98,17 +111,17 @@ const (
|
|||||||
|
|
||||||
// IsArchivePath checks if the (possibly compressed) file at the given path
|
// IsArchivePath checks if the (possibly compressed) file at the given path
|
||||||
// starts with a tar file header.
|
// starts with a tar file header.
|
||||||
func IsArchivePath(path string) bool {
|
func IsArchivePath(filePath string) bool {
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer func() { _ = file.Close() }()
|
||||||
rdr, err := compression.DecompressStream(file)
|
rdr, err := compression.DecompressStream(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer rdr.Close()
|
defer func() { _ = rdr.Close() }()
|
||||||
r := tar.NewReader(rdr)
|
r := tar.NewReader(rdr)
|
||||||
_, err = r.Next()
|
_, err = r.Next()
|
||||||
return err == nil
|
return err == nil
|
||||||
@@ -129,8 +142,10 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
|
|||||||
go func() {
|
go func() {
|
||||||
tarReader := tar.NewReader(inputTarStream)
|
tarReader := tar.NewReader(inputTarStream)
|
||||||
tarWriter := tar.NewWriter(pipeWriter)
|
tarWriter := tar.NewWriter(pipeWriter)
|
||||||
defer inputTarStream.Close()
|
defer func() {
|
||||||
defer tarWriter.Close()
|
_ = tarWriter.Close()
|
||||||
|
_ = inputTarStream.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
modify := func(name string, original *tar.Header, modifier TarModifierFunc, tarReader io.Reader) error {
|
modify := func(name string, original *tar.Header, modifier TarModifierFunc, tarReader io.Reader) error {
|
||||||
header, data, err := modifier(name, original, tarReader)
|
header, data, err := modifier(name, original, tarReader)
|
||||||
@@ -164,7 +179,7 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pipeWriter.CloseWithError(err)
|
_ = pipeWriter.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,11 +187,11 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
|
|||||||
if !ok {
|
if !ok {
|
||||||
// No modifiers for this file, copy the header and data
|
// No modifiers for this file, copy the header and data
|
||||||
if err := tarWriter.WriteHeader(originalHeader); err != nil {
|
if err := tarWriter.WriteHeader(originalHeader); err != nil {
|
||||||
pipeWriter.CloseWithError(err)
|
_ = pipeWriter.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := copyWithBuffer(tarWriter, tarReader); err != nil {
|
if err := copyWithBuffer(tarWriter, tarReader); err != nil {
|
||||||
pipeWriter.CloseWithError(err)
|
_ = pipeWriter.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -184,7 +199,7 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
|
|||||||
delete(mods, originalHeader.Name)
|
delete(mods, originalHeader.Name)
|
||||||
|
|
||||||
if err := modify(originalHeader.Name, originalHeader, modifier, tarReader); err != nil {
|
if err := modify(originalHeader.Name, originalHeader, modifier, tarReader); err != nil {
|
||||||
pipeWriter.CloseWithError(err)
|
_ = pipeWriter.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,12 +207,12 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
|
|||||||
// Apply the modifiers that haven't matched any files in the archive
|
// Apply the modifiers that haven't matched any files in the archive
|
||||||
for name, modifier := range mods {
|
for name, modifier := range mods {
|
||||||
if err := modify(name, nil, modifier, nil); err != nil {
|
if err := modify(name, nil, modifier, nil); err != nil {
|
||||||
pipeWriter.CloseWithError(err)
|
_ = pipeWriter.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeWriter.Close()
|
_ = pipeWriter.Close()
|
||||||
}()
|
}()
|
||||||
return pipeReader
|
return pipeReader
|
||||||
}
|
}
|
||||||
@@ -218,7 +233,7 @@ func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, erro
|
|||||||
hdr.ModTime = hdr.ModTime.Truncate(time.Second)
|
hdr.ModTime = hdr.ModTime.Truncate(time.Second)
|
||||||
hdr.AccessTime = time.Time{}
|
hdr.AccessTime = time.Time{}
|
||||||
hdr.ChangeTime = time.Time{}
|
hdr.ChangeTime = time.Time{}
|
||||||
hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
|
hdr.Mode = chmodTarEntry(hdr.Mode)
|
||||||
hdr.Name = canonicalTarName(name, fi.IsDir())
|
hdr.Name = canonicalTarName(name, fi.IsDir())
|
||||||
return hdr, nil
|
return hdr, nil
|
||||||
}
|
}
|
||||||
@@ -227,7 +242,7 @@ const paxSchilyXattr = "SCHILY.xattr."
|
|||||||
|
|
||||||
// ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem
|
// ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem
|
||||||
// to a tar header
|
// to a tar header
|
||||||
func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error {
|
func ReadSecurityXattrToTarHeader(filePath string, hdr *tar.Header) error {
|
||||||
const (
|
const (
|
||||||
// Values based on linux/include/uapi/linux/capability.h
|
// Values based on linux/include/uapi/linux/capability.h
|
||||||
xattrCapsSz2 = 20
|
xattrCapsSz2 = 20
|
||||||
@@ -235,7 +250,7 @@ func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error {
|
|||||||
vfsCapRevision2 = 2
|
vfsCapRevision2 = 2
|
||||||
vfsCapRevision3 = 3
|
vfsCapRevision3 = 3
|
||||||
)
|
)
|
||||||
capability, _ := lgetxattr(path, "security.capability")
|
capability, _ := lgetxattr(filePath, "security.capability")
|
||||||
if capability != nil {
|
if capability != nil {
|
||||||
if capability[versionOffset] == vfsCapRevision3 {
|
if capability[versionOffset] == vfsCapRevision3 {
|
||||||
// Convert VFS_CAP_REVISION_3 to VFS_CAP_REVISION_2 as root UID makes no
|
// Convert VFS_CAP_REVISION_3 to VFS_CAP_REVISION_2 as root UID makes no
|
||||||
@@ -292,9 +307,10 @@ func canonicalTarName(name string, isDir bool) string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
// addTarFile adds to the tar archive a file from `path` as `name`
|
// addTarFile adds to the tar archive a file from `srcPath` as `name`
|
||||||
func (ta *tarAppender) addTarFile(path, name string) error {
|
func (ta *tarAppender) addTarFile(srcPath, archivePath string) error {
|
||||||
fi, err := os.Lstat(path)
|
archivePath = filepath.ToSlash(archivePath)
|
||||||
|
fi, err := os.Lstat(srcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -302,17 +318,17 @@ func (ta *tarAppender) addTarFile(path, name string) error {
|
|||||||
var link string
|
var link string
|
||||||
if fi.Mode()&os.ModeSymlink != 0 {
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
var err error
|
var err error
|
||||||
link, err = os.Readlink(path)
|
link, err = os.Readlink(srcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hdr, err := FileInfoHeader(name, fi, link)
|
hdr, err := FileInfoHeader(archivePath, fi, link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := ReadSecurityXattrToTarHeader(path, hdr); err != nil {
|
if err := ReadSecurityXattrToTarHeader(srcPath, hdr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +337,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
|
|||||||
if !fi.IsDir() && hasHardlinks(fi) {
|
if !fi.IsDir() && hasHardlinks(fi) {
|
||||||
inode, err := getInodeFromStat(fi.Sys())
|
inode, err := getInodeFromStat(fi.Sys())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("unexpected file info for %q: %w", srcPath, err)
|
||||||
}
|
}
|
||||||
// a link should have a name that it links too
|
// a link should have a name that it links too
|
||||||
// and that linked name should be first in the tar archive
|
// and that linked name should be first in the tar archive
|
||||||
@@ -330,7 +346,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
|
|||||||
hdr.Linkname = oldpath
|
hdr.Linkname = oldpath
|
||||||
hdr.Size = 0 // This Must be here for the writer math to add up!
|
hdr.Size = 0 // This Must be here for the writer math to add up!
|
||||||
} else {
|
} else {
|
||||||
ta.SeenFiles[inode] = name
|
ta.SeenFiles[inode] = hdr.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +375,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ta.WhiteoutConverter != nil {
|
if ta.WhiteoutConverter != nil {
|
||||||
wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi)
|
wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, srcPath, fi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -370,12 +386,12 @@ func (ta *tarAppender) addTarFile(path, name string) error {
|
|||||||
// hdr may have been updated to be a whiteout with returning
|
// hdr may have been updated to be a whiteout with returning
|
||||||
// a whiteout header
|
// a whiteout header
|
||||||
if wo != nil {
|
if wo != nil {
|
||||||
|
if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
|
||||||
|
return fmt.Errorf("tar: cannot use whiteout for non-empty file %q", hdr.Name)
|
||||||
|
}
|
||||||
if err := ta.TarWriter.WriteHeader(hdr); err != nil {
|
if err := ta.TarWriter.WriteHeader(hdr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
|
|
||||||
return fmt.Errorf("tar: cannot use whiteout for non-empty file")
|
|
||||||
}
|
|
||||||
hdr = wo
|
hdr = wo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,13 +403,13 @@ func (ta *tarAppender) addTarFile(path, name string) error {
|
|||||||
if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
|
if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
|
||||||
// We use sequential file access to avoid depleting the standby list on
|
// We use sequential file access to avoid depleting the standby list on
|
||||||
// Windows. On Linux, this equates to a regular os.Open.
|
// Windows. On Linux, this equates to a regular os.Open.
|
||||||
file, err := sequential.Open(path)
|
file, err := sequential.Open(srcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = copyWithBuffer(ta.TarWriter, file)
|
err = copyWithBuffer(ta.TarWriter, file)
|
||||||
file.Close()
|
_ = file.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -402,7 +418,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error {
|
func createTarFile(dstPath, extractDir string, hdr *tar.Header, reader io.Reader, opts *TarOptions) error {
|
||||||
var (
|
var (
|
||||||
Lchown = true
|
Lchown = true
|
||||||
inUserns, bestEffortXattrs bool
|
inUserns, bestEffortXattrs bool
|
||||||
@@ -426,8 +442,8 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
|
|||||||
case tar.TypeDir:
|
case tar.TypeDir:
|
||||||
// Create directory unless it exists as a directory already.
|
// Create directory unless it exists as a directory already.
|
||||||
// In that case we just want to merge the two
|
// In that case we just want to merge the two
|
||||||
if fi, err := os.Lstat(path); err != nil || !fi.IsDir() {
|
if fi, err := os.Lstat(dstPath); err != nil || !fi.IsDir() {
|
||||||
if err := os.Mkdir(path, hdrInfo.Mode()); err != nil {
|
if err := os.Mkdir(dstPath, hdrInfo.Mode()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -435,7 +451,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
|
|||||||
case tar.TypeReg:
|
case tar.TypeReg:
|
||||||
// Source is regular file. We use sequential file access to avoid depleting
|
// 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.
|
// 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())
|
file, err := sequential.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -447,20 +463,20 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
|
|||||||
|
|
||||||
case tar.TypeBlock, tar.TypeChar:
|
case tar.TypeBlock, tar.TypeChar:
|
||||||
if inUserns { // cannot create devices in a userns
|
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")
|
log.G(context.TODO()).WithFields(log.Fields{"path": dstPath, "type": hdr.Typeflag}).Debug("skipping device nodes in a userns")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Handle this is an OS-specific way
|
// Handle this is an OS-specific way
|
||||||
if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
|
if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case tar.TypeFifo:
|
case tar.TypeFifo:
|
||||||
// Handle this is an OS-specific way
|
// Handle this is an OS-specific way
|
||||||
if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
|
if err := handleTarTypeBlockCharFifo(hdr, dstPath); err != nil {
|
||||||
if inUserns && errors.Is(err, syscall.EPERM) {
|
if inUserns && errors.Is(err, syscall.EPERM) {
|
||||||
// In most cases, cannot create a fifo if running in user namespace
|
// 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")
|
log.G(context.TODO()).WithFields(log.Fields{"error": err, "path": dstPath, "type": hdr.Typeflag}).Debug("creating fifo node in a userns")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@@ -468,26 +484,26 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
|
|||||||
|
|
||||||
case tar.TypeLink:
|
case tar.TypeLink:
|
||||||
// #nosec G305 -- The target path is checked for path traversal.
|
// #nosec G305 -- The target path is checked for path traversal.
|
||||||
targetPath := filepath.Join(extractDir, hdr.Linkname)
|
linkTarget := filepath.Join(extractDir, hdr.Linkname)
|
||||||
// check for hardlink breakout
|
// check for hardlink breakout
|
||||||
if !strings.HasPrefix(targetPath, extractDir) {
|
if !strings.HasPrefix(linkTarget, extractDir) {
|
||||||
return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname))
|
return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", linkTarget, hdr.Linkname))
|
||||||
}
|
}
|
||||||
if err := os.Link(targetPath, path); err != nil {
|
if err := os.Link(linkTarget, dstPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case tar.TypeSymlink:
|
case tar.TypeSymlink:
|
||||||
// path -> hdr.Linkname = targetPath
|
// path -> hdr.Linkname = targetPath
|
||||||
// e.g. /extractDir/path/to/symlink -> ../2/file = /extractDir/path/2/file
|
// 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.
|
targetPath := filepath.Join(filepath.Dir(dstPath), 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
|
// 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:
|
// that symlink would first have to be created, which would be caught earlier, at this very check:
|
||||||
if !strings.HasPrefix(targetPath, extractDir) {
|
if !strings.HasPrefix(targetPath, extractDir) {
|
||||||
return breakoutError(fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname))
|
return breakoutError(fmt.Errorf("invalid symlink %q -> %q", dstPath, hdr.Linkname))
|
||||||
}
|
}
|
||||||
if err := os.Symlink(hdr.Linkname, path); err != nil {
|
if err := os.Symlink(hdr.Linkname, dstPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,12 +520,12 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
|
|||||||
if chownOpts == nil {
|
if chownOpts == nil {
|
||||||
chownOpts = &ChownOpts{UID: hdr.Uid, GID: hdr.Gid}
|
chownOpts = &ChownOpts{UID: hdr.Uid, GID: hdr.Gid}
|
||||||
}
|
}
|
||||||
if err := os.Lchown(path, chownOpts.UID, chownOpts.GID); err != nil {
|
if err := os.Lchown(dstPath, chownOpts.UID, chownOpts.GID); err != nil {
|
||||||
var msg string
|
var msg string
|
||||||
if inUserns && errors.Is(err, syscall.EINVAL) {
|
if inUserns && errors.Is(err, syscall.EINVAL) {
|
||||||
msg = " (try increasing the number of subordinate IDs in /etc/subuid and /etc/subgid)"
|
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)
|
return fmt.Errorf("failed to Lchown %q for UID %d, GID %d%s: %w", dstPath, hdr.Uid, hdr.Gid, msg, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,7 +535,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := lsetxattr(path, xattr, []byte(value), 0); err != nil {
|
if err := lsetxattr(dstPath, xattr, []byte(value), 0); err != nil {
|
||||||
if bestEffortXattrs && errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.EPERM) {
|
if bestEffortXattrs && errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.EPERM) {
|
||||||
// EPERM occurs if modifying xattrs is not allowed. This can
|
// EPERM occurs if modifying xattrs is not allowed. This can
|
||||||
// happen when running in userns with restrictions (ChromeOS).
|
// happen when running in userns with restrictions (ChromeOS).
|
||||||
@@ -538,39 +554,43 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
|
|||||||
|
|
||||||
// There is no LChmod, so ignore mode for symlink. Also, this
|
// There is no LChmod, so ignore mode for symlink. Also, this
|
||||||
// must happen after chown, as that can modify the file mode
|
// must happen after chown, as that can modify the file mode
|
||||||
if err := handleLChmod(hdr, path, hdrInfo); err != nil {
|
if err := handleLChmod(hdr, dstPath, hdrInfo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
aTime := boundTime(latestTime(hdr.AccessTime, hdr.ModTime))
|
aTime := boundTime(latestTime(hdr.AccessTime, hdr.ModTime))
|
||||||
mTime := boundTime(hdr.ModTime)
|
mTime := boundTime(hdr.ModTime)
|
||||||
|
|
||||||
// chtimes doesn't support a NOFOLLOW flag atm
|
switch hdr.Typeflag {
|
||||||
if hdr.Typeflag == tar.TypeLink {
|
case tar.TypeSymlink:
|
||||||
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
|
// Apply timestamps to the symlink itself (AT_SYMLINK_NOFOLLOW).
|
||||||
if err := chtimes(path, aTime, mTime); err != nil {
|
if err := lchtimes(dstPath, aTime, mTime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case tar.TypeLink:
|
||||||
|
// Follow the hardlink only when its target is not itself a symlink.
|
||||||
|
fi, err := os.Lstat(hdr.Linkname)
|
||||||
|
if err == nil && fi.Mode()&os.ModeSymlink == 0 {
|
||||||
|
if err := chtimes(dstPath, aTime, mTime); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if hdr.Typeflag != tar.TypeSymlink {
|
default:
|
||||||
if err := chtimes(path, aTime, mTime); err != nil {
|
// All other file types follow symlinks.
|
||||||
return err
|
if err := chtimes(dstPath, aTime, mTime); err != nil {
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := lchtimes(path, aTime, mTime); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tar creates an archive from the directory at `path`, and returns it as a
|
// Tar creates an archive from the directory at `srcPath`, and returns it as a
|
||||||
// stream of bytes.
|
// stream of bytes.
|
||||||
func Tar(path string, comp compression.Compression) (io.ReadCloser, error) {
|
func Tar(srcPath string, comp compression.Compression) (io.ReadCloser, error) {
|
||||||
return TarWithOptions(path, &TarOptions{Compression: comp})
|
return TarWithOptions(srcPath, &TarOptions{Compression: comp})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TarWithOptions creates an archive from the directory at `path`, only including files whose relative
|
// TarWithOptions creates an archive from the directory at `srcPath`, only including files whose relative
|
||||||
// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`.
|
// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`.
|
||||||
func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
|
func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
|
||||||
tb, err := NewTarballer(srcPath, options)
|
tb, err := NewTarballer(srcPath, options)
|
||||||
@@ -805,6 +825,9 @@ func (t *Tarballer) Do() {
|
|||||||
|
|
||||||
// Unpack unpacks the decompressedArchive to dest with options.
|
// Unpack unpacks the decompressedArchive to dest with options.
|
||||||
func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error {
|
func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error {
|
||||||
|
if options == nil {
|
||||||
|
options = &TarOptions{}
|
||||||
|
}
|
||||||
tr := tar.NewReader(decompressedArchive)
|
tr := tar.NewReader(decompressedArchive)
|
||||||
|
|
||||||
var dirs []*tar.Header
|
var dirs []*tar.Header
|
||||||
@@ -846,8 +869,8 @@ loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// #nosec G305 -- The joined path is checked for path traversal.
|
// #nosec G305 -- The joined path is checked for path traversal.
|
||||||
path := filepath.Join(dest, hdr.Name)
|
dstPath := filepath.Join(dest, hdr.Name)
|
||||||
rel, err := filepath.Rel(dest, path)
|
rel, err := filepath.Rel(dest, dstPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -855,21 +878,21 @@ loop:
|
|||||||
return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
|
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
|
// If dstPath exists we almost always just want to remove and replace it.
|
||||||
// The only exception is when it is a directory *and* the file from
|
// 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.
|
// the layer is also a directory. Then we want to merge them (i.e.
|
||||||
// just apply the metadata from the layer).
|
// just apply the metadata from the layer).
|
||||||
if fi, err := os.Lstat(path); err == nil {
|
if fi, err := os.Lstat(dstPath); err == nil {
|
||||||
if options.NoOverwriteDirNonDir && fi.IsDir() && hdr.Typeflag != tar.TypeDir {
|
if options.NoOverwriteDirNonDir && fi.IsDir() && hdr.Typeflag != tar.TypeDir {
|
||||||
// If NoOverwriteDirNonDir is true then we cannot replace
|
// If NoOverwriteDirNonDir is true then we cannot replace
|
||||||
// an existing directory with a non-directory from the archive.
|
// an existing directory with a non-directory from the archive.
|
||||||
return fmt.Errorf("cannot overwrite directory %q with non-directory %q", path, dest)
|
return fmt.Errorf("cannot overwrite directory %q with non-directory %q", dstPath, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.NoOverwriteDirNonDir && !fi.IsDir() && hdr.Typeflag == tar.TypeDir {
|
if options.NoOverwriteDirNonDir && !fi.IsDir() && hdr.Typeflag == tar.TypeDir {
|
||||||
// If NoOverwriteDirNonDir is true then we cannot replace
|
// If NoOverwriteDirNonDir is true then we cannot replace
|
||||||
// an existing non-directory with a directory from the archive.
|
// an existing non-directory with a directory from the archive.
|
||||||
return fmt.Errorf("cannot overwrite non-directory %q with directory %q", path, dest)
|
return fmt.Errorf("cannot overwrite non-directory %q with directory %q", dstPath, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.IsDir() && hdr.Name == "." {
|
if fi.IsDir() && hdr.Name == "." {
|
||||||
@@ -877,7 +900,7 @@ loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !fi.IsDir() || hdr.Typeflag != tar.TypeDir {
|
if !fi.IsDir() || hdr.Typeflag != tar.TypeDir {
|
||||||
if err := os.RemoveAll(path); err != nil {
|
if err := os.RemoveAll(dstPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -888,7 +911,7 @@ loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if whiteoutConverter != nil {
|
if whiteoutConverter != nil {
|
||||||
writeFile, err := whiteoutConverter.ConvertRead(hdr, path)
|
writeFile, err := whiteoutConverter.ConvertRead(hdr, dstPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -897,7 +920,7 @@ loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := createTarFile(path, dest, hdr, tr, options); err != nil {
|
if err := createTarFile(dstPath, dest, hdr, tr, options); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -910,9 +933,8 @@ loop:
|
|||||||
|
|
||||||
for _, hdr := range dirs {
|
for _, hdr := range dirs {
|
||||||
// #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice.
|
// #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice.
|
||||||
path := filepath.Join(dest, hdr.Name)
|
dstPath := filepath.Join(dest, hdr.Name)
|
||||||
|
if err := chtimes(dstPath, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil {
|
||||||
if err := chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -923,16 +945,15 @@ loop:
|
|||||||
// not already exist. This is possible as the tar format supports 'implicit' directories, where their existence is
|
// 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
|
// 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.
|
// 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 {
|
func createImpliedDirectories(dest string, hdr *tar.Header, options *TarOptions) error {
|
||||||
// Not the root directory, ensure that the parent directory exists
|
// For non-directory entries, ensure that the parent directory exists.
|
||||||
if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) {
|
if hdr.Typeflag != tar.TypeDir {
|
||||||
parent := filepath.Dir(hdr.Name)
|
parent := filepath.Dir(hdr.Name)
|
||||||
parentPath := filepath.Join(dest, parent)
|
parentPath := filepath.Join(dest, parent)
|
||||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
||||||
|
if options.NoLchown {
|
||||||
|
return os.MkdirAll(parentPath, ImpliedDirectoryMode)
|
||||||
|
}
|
||||||
// RootPair() is confined inside this loop as most cases will not require a call, so we can spend some
|
// 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
|
// unneeded function calls in the uncommon case to encapsulate logic -- implied directories are a niche
|
||||||
// usage that reduces the portability of an image.
|
// usage that reduces the portability of an image.
|
||||||
@@ -974,9 +995,6 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp
|
|||||||
if options == nil {
|
if options == nil {
|
||||||
options = &TarOptions{}
|
options = &TarOptions{}
|
||||||
}
|
}
|
||||||
if options.ExcludePatterns == nil {
|
|
||||||
options.ExcludePatterns = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
r := tarArchive
|
r := tarArchive
|
||||||
if decompress {
|
if decompress {
|
||||||
@@ -984,7 +1002,7 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer decompressedArchive.Close()
|
defer func() { _ = decompressedArchive.Close() }()
|
||||||
r = decompressedArchive
|
r = decompressedArchive
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -998,7 +1016,7 @@ func (archiver *Archiver) TarUntar(src, dst string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer archive.Close()
|
defer func() { _ = archive.Close() }()
|
||||||
return archiver.Untar(archive, dst, &TarOptions{
|
return archiver.Untar(archive, dst, &TarOptions{
|
||||||
IDMap: archiver.IDMapping,
|
IDMap: archiver.IDMapping,
|
||||||
})
|
})
|
||||||
@@ -1010,7 +1028,7 @@ func (archiver *Archiver) UntarPath(src, dst string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer archive.Close()
|
defer func() { _ = archive.Close() }()
|
||||||
return archiver.Untar(archive, dst, &TarOptions{
|
return archiver.Untar(archive, dst, &TarOptions{
|
||||||
IDMap: archiver.IDMapping,
|
IDMap: archiver.IDMapping,
|
||||||
})
|
})
|
||||||
@@ -1070,13 +1088,13 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
|
|||||||
defer close(errC)
|
defer close(errC)
|
||||||
|
|
||||||
errC <- func() error {
|
errC <- func() error {
|
||||||
defer w.Close()
|
defer func() { _ = w.Close() }()
|
||||||
|
|
||||||
srcF, err := os.Open(src)
|
srcF, err := os.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer srcF.Close()
|
defer func() { _ = srcF.Close() }()
|
||||||
|
|
||||||
hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "")
|
hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1087,14 +1105,14 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
|
|||||||
hdr.AccessTime = time.Time{}
|
hdr.AccessTime = time.Time{}
|
||||||
hdr.ChangeTime = time.Time{}
|
hdr.ChangeTime = time.Time{}
|
||||||
hdr.Name = filepath.Base(dst)
|
hdr.Name = filepath.Base(dst)
|
||||||
hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
|
hdr.Mode = chmodTarEntry(hdr.Mode)
|
||||||
|
|
||||||
if err := remapIDs(archiver.IDMapping, hdr); err != nil {
|
if err := remapIDs(archiver.IDMapping, hdr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tw := tar.NewWriter(w)
|
tw := tar.NewWriter(w)
|
||||||
defer tw.Close()
|
defer func() { _ = tw.Close() }()
|
||||||
if err := tw.WriteHeader(hdr); err != nil {
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1112,7 +1130,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
|
|||||||
|
|
||||||
err = archiver.Untar(r, filepath.Dir(dst), nil)
|
err = archiver.Untar(r, filepath.Dir(dst), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.CloseWithError(err)
|
_ = r.CloseWithError(err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-38
@@ -4,6 +4,7 @@ import (
|
|||||||
"archive/tar"
|
"archive/tar"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -13,36 +14,43 @@ import (
|
|||||||
|
|
||||||
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
|
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
|
||||||
if format == OverlayWhiteoutFormat {
|
if format == OverlayWhiteoutFormat {
|
||||||
return overlayWhiteoutConverter{}
|
return newOverlayWhiteoutConverter()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type overlayWhiteoutConverter struct{}
|
type overlayWhiteoutConverter struct {
|
||||||
|
opaqueXattr string
|
||||||
|
}
|
||||||
|
|
||||||
func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, _ error) {
|
func newOverlayWhiteoutConverter() overlayWhiteoutConverter {
|
||||||
|
opaqueXattr := "trusted.overlay.opaque"
|
||||||
|
if userns.RunningInUserNS() {
|
||||||
|
opaqueXattr = "user.overlay.opaque"
|
||||||
|
}
|
||||||
|
return overlayWhiteoutConverter{
|
||||||
|
opaqueXattr: opaqueXattr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, filePath string, fi os.FileInfo) (wo *tar.Header, _ error) {
|
||||||
// convert whiteouts to AUFS format
|
// convert whiteouts to AUFS format
|
||||||
if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 {
|
if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 {
|
||||||
// we just rename the file and make it normal
|
// we just rename the file and make it normal
|
||||||
dir, filename := filepath.Split(hdr.Name)
|
dir, filename := path.Split(hdr.Name)
|
||||||
hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename)
|
hdr.Name = path.Join(dir, WhiteoutPrefix+filename)
|
||||||
hdr.Mode = 0o600
|
hdr.Mode = 0o600
|
||||||
hdr.Typeflag = tar.TypeReg
|
hdr.Typeflag = tar.TypeReg
|
||||||
hdr.Size = 0
|
hdr.Size = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.Mode()&os.ModeDir == 0 {
|
if !fi.IsDir() {
|
||||||
// FIXME(thaJeztah): return a sentinel error instead of nil, nil
|
// FIXME(thaJeztah): return a sentinel error instead of nil, nil
|
||||||
return 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
|
// convert opaque dirs to AUFS format by writing an empty file with the prefix
|
||||||
opaque, err := lgetxattr(path, opaqueXattrName)
|
opaque, err := lgetxattr(filePath, c.opaqueXattr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -50,14 +58,14 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
|
|||||||
// FIXME(thaJeztah): return a sentinel error instead of nil, nil
|
// FIXME(thaJeztah): return a sentinel error instead of nil, nil
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
delete(hdr.PAXRecords, paxSchilyXattr+opaqueXattrName)
|
delete(hdr.PAXRecords, paxSchilyXattr+c.opaqueXattr)
|
||||||
|
|
||||||
// create a header for the whiteout file
|
// create a header for the whiteout file
|
||||||
// it should inherit some properties from the parent, but be a regular file
|
// it should inherit some properties from the parent, but be a regular file
|
||||||
return &tar.Header{
|
return &tar.Header{
|
||||||
Typeflag: tar.TypeReg,
|
Typeflag: tar.TypeReg,
|
||||||
Mode: hdr.Mode & int64(os.ModePerm),
|
Mode: hdr.Mode & int64(os.ModePerm),
|
||||||
Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted.
|
Name: path.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted.
|
||||||
Size: 0,
|
Size: 0,
|
||||||
Uid: hdr.Uid,
|
Uid: hdr.Uid,
|
||||||
Uname: hdr.Uname,
|
Uname: hdr.Uname,
|
||||||
@@ -68,40 +76,54 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) {
|
func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, filePath string) (bool, error) {
|
||||||
base := filepath.Base(path)
|
base := filepath.Base(filePath)
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(filePath)
|
||||||
|
|
||||||
// if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay
|
switch base {
|
||||||
if base == WhiteoutOpaqueDir {
|
case WhiteoutPrefix, WhiteoutPrefix + ".", WhiteoutPrefix + "..":
|
||||||
opaqueXattrName := "trusted.overlay.opaque"
|
return false, fmt.Errorf("invalid whiteout entry %q", hdr.Name)
|
||||||
if userns.RunningInUserNS() {
|
|
||||||
opaqueXattrName = "user.overlay.opaque"
|
|
||||||
}
|
|
||||||
|
|
||||||
err := unix.Setxattr(dir, opaqueXattrName, []byte{'y'}, 0)
|
case WhiteoutOpaqueDir:
|
||||||
if err != nil {
|
// If a directory is marked as opaque by the AUFS special file, we need to translate that to overlay.
|
||||||
return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, opaqueXattrName, err)
|
if err := unix.Setxattr(dir, c.opaqueXattr, []byte{'y'}, 0); err != nil {
|
||||||
}
|
return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, c.opaqueXattr, err)
|
||||||
// don't write the file itself
|
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
// Don't write the whiteout file itself.
|
||||||
|
return false, nil
|
||||||
|
|
||||||
// if a file was deleted and we are using overlay, we need to create a character device
|
default:
|
||||||
if strings.HasPrefix(base, WhiteoutPrefix) {
|
originalBase, ok := strings.CutPrefix(base, WhiteoutPrefix)
|
||||||
originalBase := base[len(WhiteoutPrefix):]
|
if !ok {
|
||||||
|
// Regular file.
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
// If a file was deleted, and we are using overlay, we need to create a character device.
|
||||||
originalPath := filepath.Join(dir, originalBase)
|
originalPath := filepath.Join(dir, originalBase)
|
||||||
|
|
||||||
if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil {
|
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)
|
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 {
|
|
||||||
|
// Header IDs have already been remapped. Optimize the common non-remapped
|
||||||
|
// root-owned (0:0) case by assuming the created whiteout has the expected
|
||||||
|
// ownership, rather than comparing against the effective UID/GID or stat'ing
|
||||||
|
// the created node to verify it.
|
||||||
|
if hdr.Uid != 0 || hdr.Gid != 0 {
|
||||||
|
// TODO(thaJeztah): Revisit whether whiteout ownership needs to be preserved.
|
||||||
|
//
|
||||||
|
// This was added in the original overlay whiteout implementation:
|
||||||
|
// https://github.com/moby/moby/pull/18560 / https://github.com/moby/moby/pull/22126
|
||||||
|
//
|
||||||
|
// OverlayFS documents whiteouts in terms of a character device with device
|
||||||
|
// number 0:0, not ownership: https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories
|
||||||
|
//
|
||||||
|
// If ownership is not required, this Lchown can be removed to avoid the remaining TOCTOU window.
|
||||||
|
if err := os.Lchown(originalPath, hdr.Uid, hdr.Gid); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't write the file itself
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
// Don't write the whiteout file itself.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-11
@@ -5,6 +5,8 @@ package archive
|
|||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -13,6 +15,8 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errInvalidArchive = errors.New("invalid archive")
|
||||||
|
|
||||||
// addLongPathPrefix adds the Windows long path prefix to the path provided if
|
// 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.
|
// it does not already have it. It is a no-op on platforms other than Windows.
|
||||||
func addLongPathPrefix(srcPath string) string {
|
func addLongPathPrefix(srcPath string) string {
|
||||||
@@ -29,20 +33,19 @@ func getWalkRoot(srcPath string, include string) string {
|
|||||||
|
|
||||||
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
||||||
// on the platform the archival is done.
|
// on the platform the archival is done.
|
||||||
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
func chmodTarEntry(mode int64) int64 {
|
||||||
return perm // noop for unix as golang APIs provide perm bits correctly
|
return mode // noop for unix as golang APIs provide perm bits correctly
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInodeFromStat(stat interface{}) (uint64, error) {
|
func getInodeFromStat(stat any) (uint64, error) {
|
||||||
s, ok := stat.(*syscall.Stat_t)
|
s, ok := stat.(*syscall.Stat_t)
|
||||||
if !ok {
|
if !ok {
|
||||||
// FIXME(thaJeztah): this should likely return an error; see https://github.com/moby/moby/pull/49493#discussion_r1979152897
|
return 0, fmt.Errorf("unexpected stat type %T", stat)
|
||||||
return 0, nil
|
|
||||||
}
|
}
|
||||||
return s.Ino, nil
|
return s.Ino, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFileUIDGID(stat interface{}) (int, int, error) {
|
func getFileUIDGID(stat any) (int, int, error) {
|
||||||
s, ok := stat.(*syscall.Stat_t)
|
s, ok := stat.(*syscall.Stat_t)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -56,7 +59,7 @@ func getFileUIDGID(stat interface{}) (int, int, error) {
|
|||||||
//
|
//
|
||||||
// Creating device nodes is not supported when running in a user namespace,
|
// Creating device nodes is not supported when running in a user namespace,
|
||||||
// produces a [syscall.EPERM] in most cases.
|
// produces a [syscall.EPERM] in most cases.
|
||||||
func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
|
func handleTarTypeBlockCharFifo(hdr *tar.Header, dstPath string) error {
|
||||||
mode := uint32(hdr.Mode & 0o7777)
|
mode := uint32(hdr.Mode & 0o7777)
|
||||||
switch hdr.Typeflag {
|
switch hdr.Typeflag {
|
||||||
case tar.TypeBlock:
|
case tar.TypeBlock:
|
||||||
@@ -67,18 +70,28 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
|
|||||||
mode |= unix.S_IFIFO
|
mode |= unix.S_IFIFO
|
||||||
}
|
}
|
||||||
|
|
||||||
return mknod(path, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor)))
|
// Devmajor and Devminor come straight from the (untrusted) tar header as
|
||||||
|
// int64, but Mkdev only takes uint32. Casting a value that does not fit
|
||||||
|
// silently truncates it, so the node created on disk would carry a
|
||||||
|
// different major/minor than the header declares. Reject those instead of
|
||||||
|
// creating a mismatched device.
|
||||||
|
if hdr.Devmajor < 0 || hdr.Devmajor > math.MaxUint32 ||
|
||||||
|
hdr.Devminor < 0 || hdr.Devminor > math.MaxUint32 {
|
||||||
|
return fmt.Errorf("device number %d:%d for %q out of range: %w", hdr.Devmajor, hdr.Devminor, hdr.Name, errInvalidArchive)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mknod(dstPath, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
|
func handleLChmod(hdr *tar.Header, dstPath string, hdrInfo os.FileInfo) error {
|
||||||
if hdr.Typeflag == tar.TypeLink {
|
if hdr.Typeflag == tar.TypeLink {
|
||||||
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
|
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
|
||||||
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
|
if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if hdr.Typeflag != tar.TypeSymlink {
|
} else if hdr.Typeflag != tar.TypeSymlink {
|
||||||
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
|
if err := os.Chmod(dstPath, hdrInfo.Mode()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -33,15 +33,15 @@ func getWalkRoot(srcPath string, include string) string {
|
|||||||
|
|
||||||
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
||||||
// on the platform the archival is done.
|
// on the platform the archival is done.
|
||||||
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
func chmodTarEntry(mode int64) int64 {
|
||||||
// Remove group- and world-writable bits.
|
// Remove group- and world-writable bits.
|
||||||
perm &= 0o755
|
mode &= 0o755
|
||||||
|
|
||||||
// Add the x bit: make everything +x on Windows
|
// Add the x bit: make everything +x on Windows
|
||||||
return perm | 0o111
|
return mode | 0o111
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInodeFromStat(stat interface{}) (uint64, error) {
|
func getInodeFromStat(stat any) (uint64, error) {
|
||||||
// do nothing. no notion of Inode in stat on Windows
|
// do nothing. no notion of Inode in stat on Windows
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFileUIDGID(stat interface{}) (int, int, error) {
|
func getFileUIDGID(stat any) (int, int, error) {
|
||||||
// no notion of file ownership mapping yet on Windows
|
// no notion of file ownership mapping yet on Windows
|
||||||
return 0, 0, nil
|
return 0, 0, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-9
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -217,8 +218,8 @@ func (info *FileInfo) LookUp(path string) *FileInfo {
|
|||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
pathElements := strings.Split(path, string(os.PathSeparator))
|
pathElements := strings.SplitSeq(path, string(os.PathSeparator))
|
||||||
for _, elem := range pathElements {
|
for elem := range pathElements {
|
||||||
if elem != "" {
|
if elem != "" {
|
||||||
child := parent.children[elem]
|
child := parent.children[elem]
|
||||||
if child == nil {
|
if child == nil {
|
||||||
@@ -256,9 +257,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
|
|||||||
// otherwise any previous delete/change is considered recursive
|
// otherwise any previous delete/change is considered recursive
|
||||||
oldChildren := make(map[string]*FileInfo)
|
oldChildren := make(map[string]*FileInfo)
|
||||||
if oldInfo != nil && info.isDir() {
|
if oldInfo != nil && info.isDir() {
|
||||||
for k, v := range oldInfo.children {
|
maps.Copy(oldChildren, oldInfo.children)
|
||||||
oldChildren[k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, newChild := range info.children {
|
for name, newChild := range info.children {
|
||||||
@@ -401,7 +400,7 @@ func ExportChanges(dir string, changes []Change, idMap user.IdentityMapping) (io
|
|||||||
whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase)
|
whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase)
|
||||||
timestamp := time.Now()
|
timestamp := time.Now()
|
||||||
hdr := &tar.Header{
|
hdr := &tar.Header{
|
||||||
Name: whiteOut[1:],
|
Name: strings.TrimPrefix(filepath.ToSlash(whiteOut), "/"),
|
||||||
Size: 0,
|
Size: 0,
|
||||||
ModTime: timestamp,
|
ModTime: timestamp,
|
||||||
AccessTime: timestamp,
|
AccessTime: timestamp,
|
||||||
@@ -411,9 +410,10 @@ func ExportChanges(dir string, changes []Change, idMap user.IdentityMapping) (io
|
|||||||
log.G(context.TODO()).Debugf("Can't write whiteout header: %s", err)
|
log.G(context.TODO()).Debugf("Can't write whiteout header: %s", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
path := filepath.Join(dir, change.Path)
|
srcPath := filepath.Join(dir, change.Path)
|
||||||
if err := ta.addTarFile(path, change.Path[1:]); err != nil {
|
archivePath := strings.TrimPrefix(filepath.ToSlash(change.Path), "/")
|
||||||
log.G(context.TODO()).Debugf("Can't add file %s to tar: %s", path, err)
|
if err := ta.addTarFile(srcPath, archivePath); err != nil {
|
||||||
|
log.G(context.TODO()).Debugf("Can't add file %s to tar: %s", srcPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -265,7 +265,7 @@ func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func clen(n []byte) int {
|
func clen(n []byte) int {
|
||||||
for i := 0; i < len(n); i++ {
|
for i := range n {
|
||||||
if n[i] == 0 {
|
if n[i] == 0 {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ func collectFileInfoForChanges(oldDir, newDir string) (*FileInfo, *FileInfo, err
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// block until both routines have returned
|
// block until both routines have returned
|
||||||
for i := 0; i < 2; i++ {
|
for range 2 {
|
||||||
if err := <-errs; err != nil {
|
if err := <-errs; err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -66,7 +66,7 @@ type nopWriteCloser struct {
|
|||||||
func (nopWriteCloser) Close() error { return nil }
|
func (nopWriteCloser) Close() error { return nil }
|
||||||
|
|
||||||
var bufioReader32KPool = &sync.Pool{
|
var bufioReader32KPool = &sync.Pool{
|
||||||
New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) },
|
New: func() any { return bufio.NewReaderSize(nil, 32*1024) },
|
||||||
}
|
}
|
||||||
|
|
||||||
type bufferedReader struct {
|
type bufferedReader struct {
|
||||||
@@ -217,7 +217,7 @@ func gzipDecompress(ctx context.Context, buf io.Reader) (io.ReadCloser, error) {
|
|||||||
|
|
||||||
log.G(ctx).Debugf("Using %s to decompress", unpigzPath)
|
log.G(ctx).Debugf("Using %s to decompress", unpigzPath)
|
||||||
|
|
||||||
return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf)
|
return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf) // #nosec G204 -- Subprocess launched with variable
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmdStream executes a command, and returns its stdout as a stream.
|
// cmdStream executes a command, and returns its stdout as a stream.
|
||||||
|
|||||||
+14
-13
@@ -22,7 +22,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var copyPool = sync.Pool{
|
var copyPool = sync.Pool{
|
||||||
New: func() interface{} { s := make([]byte, 32*1024); return &s },
|
New: func() any { s := make([]byte, 32*1024); return &s },
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyWithBuffer(dst io.Writer, src io.Reader) error {
|
func copyWithBuffer(dst io.Writer, src io.Reader) error {
|
||||||
@@ -319,7 +319,10 @@ func PrepareArchiveCopy(srcContent io.Reader, srcInfo, dstInfo CopyInfo) (dstDir
|
|||||||
// RebaseArchiveEntries rewrites the given srcContent archive replacing
|
// RebaseArchiveEntries rewrites the given srcContent archive replacing
|
||||||
// an occurrence of oldBase with newBase at the beginning of entry names.
|
// an occurrence of oldBase with newBase at the beginning of entry names.
|
||||||
func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.ReadCloser {
|
func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.ReadCloser {
|
||||||
if oldBase == string(os.PathSeparator) {
|
oldBase = filepath.ToSlash(oldBase)
|
||||||
|
newBase = filepath.ToSlash(newBase)
|
||||||
|
|
||||||
|
if oldBase == "/" {
|
||||||
// If oldBase specifies the root directory, use an empty string as
|
// If oldBase specifies the root directory, use an empty string as
|
||||||
// oldBase instead so that newBase doesn't replace the path separator
|
// oldBase instead so that newBase doesn't replace the path separator
|
||||||
// that all paths will start with.
|
// that all paths will start with.
|
||||||
@@ -336,12 +339,12 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read
|
|||||||
hdr, err := srcTar.Next()
|
hdr, err := srcTar.Next()
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
// Signals end of archive.
|
// Signals end of archive.
|
||||||
rebasedTar.Close()
|
_ = rebasedTar.Close()
|
||||||
w.Close()
|
_ = w.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.CloseWithError(err)
|
_ = w.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +362,7 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err = rebasedTar.WriteHeader(hdr); err != nil {
|
if err = rebasedTar.WriteHeader(hdr); err != nil {
|
||||||
w.CloseWithError(err)
|
_ = w.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,7 +377,7 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read
|
|||||||
// not be vulnerable to this code consuming memory.
|
// not be vulnerable to this code consuming memory.
|
||||||
//nolint:gosec // G110: Potential DoS vulnerability via decompression bomb (gosec)
|
//nolint:gosec // G110: Potential DoS vulnerability via decompression bomb (gosec)
|
||||||
if _, err = io.Copy(rebasedTar, srcTar); err != nil {
|
if _, err = io.Copy(rebasedTar, srcTar); err != nil {
|
||||||
w.CloseWithError(err)
|
_ = w.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -408,7 +411,7 @@ func CopyResource(srcPath, dstPath string, followLink bool) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer content.Close()
|
defer func() { _ = content.Close() }()
|
||||||
|
|
||||||
return CopyTo(content, srcInfo, dstPath)
|
return CopyTo(content, srcInfo, dstPath)
|
||||||
}
|
}
|
||||||
@@ -427,14 +430,12 @@ func CopyTo(content io.Reader, srcInfo CopyInfo, dstPath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer copyArchive.Close()
|
defer func() { _ = copyArchive.Close() }()
|
||||||
|
|
||||||
options := &TarOptions{
|
return Untar(copyArchive, dstDir, &TarOptions{
|
||||||
NoLchown: true,
|
NoLchown: true,
|
||||||
NoOverwriteDirNonDir: true,
|
NoOverwriteDirNonDir: true,
|
||||||
}
|
})
|
||||||
|
|
||||||
return Untar(copyArchive, dstDir, options)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveHostSourcePath decides real path need to be copied with parameters such as
|
// ResolveHostSourcePath decides real path need to be copied with parameters such as
|
||||||
|
|||||||
+1
-1
@@ -5,5 +5,5 @@ package archive
|
|||||||
import "golang.org/x/sys/unix"
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
func mknod(path string, mode uint32, dev uint64) error {
|
func mknod(path string, mode uint32, dev uint64) error {
|
||||||
return unix.Mknod(path, mode, int(dev))
|
return unix.Mknod(path, mode, int(dev)) // #nosec G115 -- Required conversion for the platform-specific Mknod API.
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-15
@@ -28,9 +28,6 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
|||||||
if options == nil {
|
if options == nil {
|
||||||
options = &TarOptions{}
|
options = &TarOptions{}
|
||||||
}
|
}
|
||||||
if options.ExcludePatterns == nil {
|
|
||||||
options.ExcludePatterns = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
aufsTempdir := ""
|
aufsTempdir := ""
|
||||||
aufsHardlinks := make(map[string]*tar.Header)
|
aufsHardlinks := make(map[string]*tar.Header)
|
||||||
@@ -102,8 +99,8 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// #nosec G305 -- The joined path is guarded against path traversal.
|
// #nosec G305 -- The joined path is guarded against path traversal.
|
||||||
path := filepath.Join(dest, hdr.Name)
|
dstPath := filepath.Join(dest, hdr.Name)
|
||||||
rel, err := filepath.Rel(dest, path)
|
rel, err := filepath.Rel(dest, dstPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -112,10 +109,10 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
|||||||
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||||
return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
|
return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
|
||||||
}
|
}
|
||||||
base := filepath.Base(path)
|
base := filepath.Base(dstPath)
|
||||||
|
|
||||||
if strings.HasPrefix(base, WhiteoutPrefix) {
|
if strings.HasPrefix(base, WhiteoutPrefix) {
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(dstPath)
|
||||||
if base == WhiteoutOpaqueDir {
|
if base == WhiteoutOpaqueDir {
|
||||||
_, err := os.Lstat(dir)
|
_, err := os.Lstat(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -132,7 +129,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if _, exists := unpackedPaths[path]; !exists {
|
if _, exists := unpackedPaths[path]; !exists {
|
||||||
return os.RemoveAll(path)
|
return os.RemoveAll(path) // #nosec G122 -- FIXME: consider root-scoped APIs (e.g. os.Root) to prevent symlink TOCTOU traversal
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -147,13 +144,13 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If path exits we almost always just want to remove and replace it.
|
// If dstPath exists we almost always just want to remove and replace it.
|
||||||
// The only exception is when it is a directory *and* the file from
|
// 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.
|
// the layer is also a directory. Then we want to merge them (i.e.
|
||||||
// just apply the metadata from the layer).
|
// just apply the metadata from the layer).
|
||||||
if fi, err := os.Lstat(path); err == nil {
|
if fi, err := os.Lstat(dstPath); err == nil {
|
||||||
if !fi.IsDir() || hdr.Typeflag != tar.TypeDir {
|
if !fi.IsDir() || hdr.Typeflag != tar.TypeDir {
|
||||||
if err := os.RemoveAll(path); err != nil {
|
if err := os.RemoveAll(dstPath); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,7 +179,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := createTarFile(path, dest, srcHdr, srcData, options); err != nil {
|
if err := createTarFile(dstPath, dest, srcHdr, srcData, options); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,14 +188,14 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
|||||||
if hdr.Typeflag == tar.TypeDir {
|
if hdr.Typeflag == tar.TypeDir {
|
||||||
dirs = append(dirs, hdr)
|
dirs = append(dirs, hdr)
|
||||||
}
|
}
|
||||||
unpackedPaths[path] = struct{}{}
|
unpackedPaths[dstPath] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, hdr := range dirs {
|
for _, hdr := range dirs {
|
||||||
// #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice.
|
// #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice.
|
||||||
path := filepath.Join(dest, hdr.Name)
|
dstPath := filepath.Join(dest, hdr.Name)
|
||||||
if err := chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
|
if err := chtimes(dstPath, hdr.AccessTime, hdr.ModTime); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ func (fi nosysFileInfo) Gname() (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fi nosysFileInfo) Sys() interface{} {
|
func (fi nosysFileInfo) Sys() any {
|
||||||
// A Sys value of type *tar.Header is safe as it is system-independent.
|
// A Sys value of type *tar.Header is safe as it is system-independent.
|
||||||
// The tar.FileInfoHeader function copies the fields into the returned
|
// The tar.FileInfoHeader function copies the fields into the returned
|
||||||
// header without performing any OS lookups.
|
// header without performing any OS lookups.
|
||||||
|
|||||||
+5
-4
@@ -36,10 +36,11 @@ func sysStat(fi os.FileInfo, hdr *tar.Header) error {
|
|||||||
hdr.Uid = int(s.Uid)
|
hdr.Uid = int(s.Uid)
|
||||||
hdr.Gid = int(s.Gid)
|
hdr.Gid = int(s.Gid)
|
||||||
|
|
||||||
if s.Mode&unix.S_IFBLK != 0 ||
|
if s.Mode&unix.S_IFBLK != 0 || s.Mode&unix.S_IFCHR != 0 {
|
||||||
s.Mode&unix.S_IFCHR != 0 {
|
// #nosec G115 -- Rdev type varies by platform.
|
||||||
hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert
|
rdev := uint64(s.Rdev) //nolint:unconvert // Rdev type varies by platform.
|
||||||
hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert
|
hdr.Devmajor = int64(unix.Major(rdev))
|
||||||
|
hdr.Devminor = int64(unix.Minor(rdev))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+10
-10
@@ -13,26 +13,26 @@ import (
|
|||||||
// lgetxattr retrieves the value of the extended attribute identified by attr
|
// lgetxattr retrieves the value of the extended attribute identified by attr
|
||||||
// and associated with the given path in the file system.
|
// and associated with the given path in the file system.
|
||||||
// It returns a nil slice and nil error if the xattr is not set.
|
// It returns a nil slice and nil error if the xattr is not set.
|
||||||
func lgetxattr(path string, attr string) ([]byte, error) {
|
func lgetxattr(filePath string, attr string) ([]byte, error) {
|
||||||
// Start with a 128 length byte array
|
// Start with a 128 length byte array
|
||||||
dest := make([]byte, 128)
|
dest := make([]byte, 128)
|
||||||
sz, err := unix.Lgetxattr(path, attr, dest)
|
sz, err := unix.Lgetxattr(filePath, attr, dest)
|
||||||
|
|
||||||
for errors.Is(err, unix.ERANGE) {
|
for errors.Is(err, unix.ERANGE) {
|
||||||
// Buffer too small, use zero-sized buffer to get the actual size
|
// Buffer too small, use zero-sized buffer to get the actual size
|
||||||
sz, err = unix.Lgetxattr(path, attr, []byte{})
|
sz, err = unix.Lgetxattr(filePath, attr, []byte{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapPathError("lgetxattr", path, attr, err)
|
return nil, wrapPathError("lgetxattr", filePath, attr, err)
|
||||||
}
|
}
|
||||||
dest = make([]byte, sz)
|
dest = make([]byte, sz)
|
||||||
sz, err = unix.Lgetxattr(path, attr, dest)
|
sz, err = unix.Lgetxattr(filePath, attr, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, noattr) {
|
if errors.Is(err, noattr) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return nil, wrapPathError("lgetxattr", path, attr, err)
|
return nil, wrapPathError("lgetxattr", filePath, attr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dest[:sz], nil
|
return dest[:sz], nil
|
||||||
@@ -40,13 +40,13 @@ func lgetxattr(path string, attr string) ([]byte, error) {
|
|||||||
|
|
||||||
// lsetxattr sets the value of the extended attribute identified by attr
|
// lsetxattr sets the value of the extended attribute identified by attr
|
||||||
// and associated with the given path in the file system.
|
// and associated with the given path in the file system.
|
||||||
func lsetxattr(path string, attr string, data []byte, flags int) error {
|
func lsetxattr(filePath string, attr string, data []byte, flags int) error {
|
||||||
return wrapPathError("lsetxattr", path, attr, unix.Lsetxattr(path, attr, data, flags))
|
return wrapPathError("lsetxattr", filePath, attr, unix.Lsetxattr(filePath, attr, data, flags))
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapPathError(op, path, attr string, err error) error {
|
func wrapPathError(op, filePath, attr string, err error) error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &fs.PathError{Op: op, Path: path, Err: fmt.Errorf("xattr %q: %w", attr, err)}
|
return &fs.PathError{Op: op, Path: filePath, Err: fmt.Errorf("xattr %q: %w", attr, err)}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+3
-3
@@ -644,7 +644,7 @@ github.com/mitchellh/go-wordwrap
|
|||||||
# github.com/mitchellh/hashstructure/v2 v2.0.2
|
# github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
## explicit; go 1.14
|
## explicit; go 1.14
|
||||||
github.com/mitchellh/hashstructure/v2
|
github.com/mitchellh/hashstructure/v2
|
||||||
# github.com/moby/buildkit v0.32.0-rc1
|
# github.com/moby/buildkit v0.32.0-rc2
|
||||||
## explicit; go 1.26.3
|
## explicit; go 1.26.3
|
||||||
github.com/moby/buildkit/api/services/control
|
github.com/moby/buildkit/api/services/control
|
||||||
github.com/moby/buildkit/api/types
|
github.com/moby/buildkit/api/types
|
||||||
@@ -747,8 +747,8 @@ github.com/moby/buildkit/version
|
|||||||
# github.com/moby/docker-image-spec v1.3.1
|
# github.com/moby/docker-image-spec v1.3.1
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/moby/docker-image-spec/specs-go/v1
|
github.com/moby/docker-image-spec/specs-go/v1
|
||||||
# github.com/moby/go-archive v0.2.0
|
# github.com/moby/go-archive v0.2.1
|
||||||
## explicit; go 1.23.0
|
## explicit; go 1.25
|
||||||
github.com/moby/go-archive
|
github.com/moby/go-archive
|
||||||
github.com/moby/go-archive/compression
|
github.com/moby/go-archive/compression
|
||||||
github.com/moby/go-archive/tarheader
|
github.com/moby/go-archive/tarheader
|
||||||
|
|||||||
Reference in New Issue
Block a user