63a9d72ee8
When writing a tar file with archive/tar, extended attributes in the deprecated (tar.Header).Xattrs map take precedence over conflicting 'SCHILY.xattr' records in the (tar.Header).PAXRecords map. Update package tarsum to follow the same precedence rules as archive/tar. Signed-off-by: Cory Snider <csnider@mirantis.com>
166 lines
4.8 KiB
Go
166 lines
4.8 KiB
Go
package tarsum // import "github.com/docker/docker/pkg/tarsum"
|
|
|
|
import (
|
|
"archive/tar"
|
|
"errors"
|
|
"io"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Version is used for versioning of the TarSum algorithm
|
|
// based on the prefix of the hash used
|
|
// i.e. "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"
|
|
type Version int
|
|
|
|
// Prefix of "tarsum"
|
|
const (
|
|
Version0 Version = iota
|
|
Version1
|
|
// VersionDev this constant will be either the latest or an unsettled next-version of the TarSum calculation
|
|
VersionDev
|
|
)
|
|
|
|
// WriteV1Header writes a tar header to a writer in V1 tarsum format.
|
|
func WriteV1Header(h *tar.Header, w io.Writer) {
|
|
for _, elem := range v1TarHeaderSelect(h) {
|
|
w.Write([]byte(elem[0] + elem[1]))
|
|
}
|
|
}
|
|
|
|
// VersionLabelForChecksum returns the label for the given tarsum
|
|
// checksum, i.e., everything before the first `+` character in
|
|
// the string or an empty string if no label separator is found.
|
|
func VersionLabelForChecksum(checksum string) string {
|
|
// Checksums are in the form: {versionLabel}+{hashID}:{hex}
|
|
sepIndex := strings.Index(checksum, "+")
|
|
if sepIndex < 0 {
|
|
return ""
|
|
}
|
|
return checksum[:sepIndex]
|
|
}
|
|
|
|
// GetVersions gets a list of all known tarsum versions.
|
|
func GetVersions() []Version {
|
|
v := []Version{}
|
|
for k := range tarSumVersions {
|
|
v = append(v, k)
|
|
}
|
|
return v
|
|
}
|
|
|
|
var (
|
|
tarSumVersions = map[Version]string{
|
|
Version0: "tarsum",
|
|
Version1: "tarsum.v1",
|
|
VersionDev: "tarsum.dev",
|
|
}
|
|
tarSumVersionsByName = map[string]Version{
|
|
"tarsum": Version0,
|
|
"tarsum.v1": Version1,
|
|
"tarsum.dev": VersionDev,
|
|
}
|
|
)
|
|
|
|
func (tsv Version) String() string {
|
|
return tarSumVersions[tsv]
|
|
}
|
|
|
|
// GetVersionFromTarsum returns the Version from the provided string.
|
|
func GetVersionFromTarsum(tarsum string) (Version, error) {
|
|
versionName, _, _ := strings.Cut(tarsum, "+")
|
|
version, ok := tarSumVersionsByName[versionName]
|
|
if !ok {
|
|
return -1, ErrNotVersion
|
|
}
|
|
return version, nil
|
|
}
|
|
|
|
// Errors that may be returned by functions in this package
|
|
var (
|
|
ErrNotVersion = errors.New("string does not include a TarSum Version")
|
|
ErrVersionNotImplemented = errors.New("TarSum Version is not yet implemented")
|
|
)
|
|
|
|
// tarHeaderSelector is the interface which different versions
|
|
// of tarsum should use for selecting and ordering tar headers
|
|
// for each item in the archive.
|
|
type tarHeaderSelector interface {
|
|
selectHeaders(h *tar.Header) (orderedHeaders [][2]string)
|
|
}
|
|
|
|
type tarHeaderSelectFunc func(h *tar.Header) (orderedHeaders [][2]string)
|
|
|
|
func (f tarHeaderSelectFunc) selectHeaders(h *tar.Header) (orderedHeaders [][2]string) {
|
|
return f(h)
|
|
}
|
|
|
|
func v0TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
|
|
return [][2]string{
|
|
{"name", h.Name},
|
|
{"mode", strconv.FormatInt(h.Mode, 10)},
|
|
{"uid", strconv.Itoa(h.Uid)},
|
|
{"gid", strconv.Itoa(h.Gid)},
|
|
{"size", strconv.FormatInt(h.Size, 10)},
|
|
{"mtime", strconv.FormatInt(h.ModTime.UTC().Unix(), 10)},
|
|
{"typeflag", string([]byte{h.Typeflag})},
|
|
{"linkname", h.Linkname},
|
|
{"uname", h.Uname},
|
|
{"gname", h.Gname},
|
|
{"devmajor", strconv.FormatInt(h.Devmajor, 10)},
|
|
{"devminor", strconv.FormatInt(h.Devminor, 10)},
|
|
}
|
|
}
|
|
|
|
func v1TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
|
|
// Get extended attributes.
|
|
const paxSchilyXattr = "SCHILY.xattr."
|
|
var xattrs [][2]string
|
|
for k, v := range h.PAXRecords {
|
|
if xattr, ok := strings.CutPrefix(k, paxSchilyXattr); ok {
|
|
// h.Xattrs keys take precedence over h.PAXRecords keys, like
|
|
// archive/tar does when writing.
|
|
if vv, ok := h.Xattrs[xattr]; ok { //nolint:staticcheck // field deprecated in stdlib
|
|
v = vv
|
|
}
|
|
xattrs = append(xattrs, [2]string{xattr, v})
|
|
}
|
|
}
|
|
// Get extended attributes which are not in PAXRecords.
|
|
for k, v := range h.Xattrs { //nolint:staticcheck // field deprecated in stdlib
|
|
if _, ok := h.PAXRecords[paxSchilyXattr+k]; !ok {
|
|
xattrs = append(xattrs, [2]string{k, v})
|
|
}
|
|
}
|
|
sort.Slice(xattrs, func(i, j int) bool { return xattrs[i][0] < xattrs[j][0] })
|
|
|
|
// Make the slice with enough capacity to hold the 11 basic headers
|
|
// we want from the v0 selector plus however many xattrs we have.
|
|
orderedHeaders = make([][2]string, 0, 11+len(xattrs))
|
|
|
|
// Copy all headers from v0 excluding the 'mtime' header (the 5th element).
|
|
v0headers := v0TarHeaderSelect(h)
|
|
orderedHeaders = append(orderedHeaders, v0headers[0:5]...)
|
|
orderedHeaders = append(orderedHeaders, v0headers[6:]...)
|
|
|
|
// Finally, append the sorted xattrs.
|
|
orderedHeaders = append(orderedHeaders, xattrs...)
|
|
|
|
return
|
|
}
|
|
|
|
var registeredHeaderSelectors = map[Version]tarHeaderSelectFunc{
|
|
Version0: v0TarHeaderSelect,
|
|
Version1: v1TarHeaderSelect,
|
|
VersionDev: v1TarHeaderSelect,
|
|
}
|
|
|
|
func getTarHeaderSelector(v Version) (tarHeaderSelector, error) {
|
|
headerSelector, ok := registeredHeaderSelectors[v]
|
|
if !ok {
|
|
return nil, ErrVersionNotImplemented
|
|
}
|
|
|
|
return headerSelector, nil
|
|
}
|