github.com/moby/docker@v26.1.3+incompatible/pkg/tarsum/versioning.go (about)

     1  package tarsum // import "github.com/docker/docker/pkg/tarsum"
     2  
     3  import (
     4  	"archive/tar"
     5  	"errors"
     6  	"io"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  )
    11  
    12  // Version is used for versioning of the TarSum algorithm
    13  // based on the prefix of the hash used
    14  // i.e. "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"
    15  type Version int
    16  
    17  // Prefix of "tarsum"
    18  const (
    19  	Version0 Version = iota
    20  	Version1
    21  	// VersionDev this constant will be either the latest or an unsettled next-version of the TarSum calculation
    22  	VersionDev
    23  )
    24  
    25  // WriteV1Header writes a tar header to a writer in V1 tarsum format.
    26  func WriteV1Header(h *tar.Header, w io.Writer) {
    27  	for _, elem := range v1TarHeaderSelect(h) {
    28  		w.Write([]byte(elem[0] + elem[1]))
    29  	}
    30  }
    31  
    32  // VersionLabelForChecksum returns the label for the given tarsum
    33  // checksum, i.e., everything before the first `+` character in
    34  // the string or an empty string if no label separator is found.
    35  func VersionLabelForChecksum(checksum string) string {
    36  	// Checksums are in the form: {versionLabel}+{hashID}:{hex}
    37  	sepIndex := strings.Index(checksum, "+")
    38  	if sepIndex < 0 {
    39  		return ""
    40  	}
    41  	return checksum[:sepIndex]
    42  }
    43  
    44  // GetVersions gets a list of all known tarsum versions.
    45  func GetVersions() []Version {
    46  	v := []Version{}
    47  	for k := range tarSumVersions {
    48  		v = append(v, k)
    49  	}
    50  	return v
    51  }
    52  
    53  var (
    54  	tarSumVersions = map[Version]string{
    55  		Version0:   "tarsum",
    56  		Version1:   "tarsum.v1",
    57  		VersionDev: "tarsum.dev",
    58  	}
    59  	tarSumVersionsByName = map[string]Version{
    60  		"tarsum":     Version0,
    61  		"tarsum.v1":  Version1,
    62  		"tarsum.dev": VersionDev,
    63  	}
    64  )
    65  
    66  func (tsv Version) String() string {
    67  	return tarSumVersions[tsv]
    68  }
    69  
    70  // GetVersionFromTarsum returns the Version from the provided string.
    71  func GetVersionFromTarsum(tarsum string) (Version, error) {
    72  	versionName, _, _ := strings.Cut(tarsum, "+")
    73  	version, ok := tarSumVersionsByName[versionName]
    74  	if !ok {
    75  		return -1, ErrNotVersion
    76  	}
    77  	return version, nil
    78  }
    79  
    80  // Errors that may be returned by functions in this package
    81  var (
    82  	ErrNotVersion            = errors.New("string does not include a TarSum Version")
    83  	ErrVersionNotImplemented = errors.New("TarSum Version is not yet implemented")
    84  )
    85  
    86  // tarHeaderSelector is the interface which different versions
    87  // of tarsum should use for selecting and ordering tar headers
    88  // for each item in the archive.
    89  type tarHeaderSelector interface {
    90  	selectHeaders(h *tar.Header) (orderedHeaders [][2]string)
    91  }
    92  
    93  type tarHeaderSelectFunc func(h *tar.Header) (orderedHeaders [][2]string)
    94  
    95  func (f tarHeaderSelectFunc) selectHeaders(h *tar.Header) (orderedHeaders [][2]string) {
    96  	return f(h)
    97  }
    98  
    99  func v0TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
   100  	return [][2]string{
   101  		{"name", h.Name},
   102  		{"mode", strconv.FormatInt(h.Mode, 10)},
   103  		{"uid", strconv.Itoa(h.Uid)},
   104  		{"gid", strconv.Itoa(h.Gid)},
   105  		{"size", strconv.FormatInt(h.Size, 10)},
   106  		{"mtime", strconv.FormatInt(h.ModTime.UTC().Unix(), 10)},
   107  		{"typeflag", string([]byte{h.Typeflag})},
   108  		{"linkname", h.Linkname},
   109  		{"uname", h.Uname},
   110  		{"gname", h.Gname},
   111  		{"devmajor", strconv.FormatInt(h.Devmajor, 10)},
   112  		{"devminor", strconv.FormatInt(h.Devminor, 10)},
   113  	}
   114  }
   115  
   116  func v1TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
   117  	// Get extended attributes.
   118  	const paxSchilyXattr = "SCHILY.xattr."
   119  	var xattrs [][2]string
   120  	for k, v := range h.PAXRecords {
   121  		if xattr, ok := strings.CutPrefix(k, paxSchilyXattr); ok {
   122  			// h.Xattrs keys take precedence over h.PAXRecords keys, like
   123  			// archive/tar does when writing.
   124  			if vv, ok := h.Xattrs[xattr]; ok { //nolint:staticcheck // field deprecated in stdlib
   125  				v = vv
   126  			}
   127  			xattrs = append(xattrs, [2]string{xattr, v})
   128  		}
   129  	}
   130  	// Get extended attributes which are not in PAXRecords.
   131  	for k, v := range h.Xattrs { //nolint:staticcheck // field deprecated in stdlib
   132  		if _, ok := h.PAXRecords[paxSchilyXattr+k]; !ok {
   133  			xattrs = append(xattrs, [2]string{k, v})
   134  		}
   135  	}
   136  	sort.Slice(xattrs, func(i, j int) bool { return xattrs[i][0] < xattrs[j][0] })
   137  
   138  	// Make the slice with enough capacity to hold the 11 basic headers
   139  	// we want from the v0 selector plus however many xattrs we have.
   140  	orderedHeaders = make([][2]string, 0, 11+len(xattrs))
   141  
   142  	// Copy all headers from v0 excluding the 'mtime' header (the 5th element).
   143  	v0headers := v0TarHeaderSelect(h)
   144  	orderedHeaders = append(orderedHeaders, v0headers[0:5]...)
   145  	orderedHeaders = append(orderedHeaders, v0headers[6:]...)
   146  
   147  	// Finally, append the sorted xattrs.
   148  	orderedHeaders = append(orderedHeaders, xattrs...)
   149  
   150  	return
   151  }
   152  
   153  var registeredHeaderSelectors = map[Version]tarHeaderSelectFunc{
   154  	Version0:   v0TarHeaderSelect,
   155  	Version1:   v1TarHeaderSelect,
   156  	VersionDev: v1TarHeaderSelect,
   157  }
   158  
   159  func getTarHeaderSelector(v Version) (tarHeaderSelector, error) {
   160  	headerSelector, ok := registeredHeaderSelectors[v]
   161  	if !ok {
   162  		return nil, ErrVersionNotImplemented
   163  	}
   164  
   165  	return headerSelector, nil
   166  }