github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/tarsum/versioning.go (about)

     1  package tarsum // import "github.com/demonoid81/moby/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  	tsv := tarsum
    73  	if strings.Contains(tarsum, "+") {
    74  		tsv = strings.SplitN(tarsum, "+", 2)[0]
    75  	}
    76  	for v, s := range tarSumVersions {
    77  		if s == tsv {
    78  			return v, nil
    79  		}
    80  	}
    81  	return -1, ErrNotVersion
    82  }
    83  
    84  // Errors that may be returned by functions in this package
    85  var (
    86  	ErrNotVersion            = errors.New("string does not include a TarSum Version")
    87  	ErrVersionNotImplemented = errors.New("TarSum Version is not yet implemented")
    88  )
    89  
    90  // tarHeaderSelector is the interface which different versions
    91  // of tarsum should use for selecting and ordering tar headers
    92  // for each item in the archive.
    93  type tarHeaderSelector interface {
    94  	selectHeaders(h *tar.Header) (orderedHeaders [][2]string)
    95  }
    96  
    97  type tarHeaderSelectFunc func(h *tar.Header) (orderedHeaders [][2]string)
    98  
    99  func (f tarHeaderSelectFunc) selectHeaders(h *tar.Header) (orderedHeaders [][2]string) {
   100  	return f(h)
   101  }
   102  
   103  func v0TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
   104  	return [][2]string{
   105  		{"name", h.Name},
   106  		{"mode", strconv.FormatInt(h.Mode, 10)},
   107  		{"uid", strconv.Itoa(h.Uid)},
   108  		{"gid", strconv.Itoa(h.Gid)},
   109  		{"size", strconv.FormatInt(h.Size, 10)},
   110  		{"mtime", strconv.FormatInt(h.ModTime.UTC().Unix(), 10)},
   111  		{"typeflag", string([]byte{h.Typeflag})},
   112  		{"linkname", h.Linkname},
   113  		{"uname", h.Uname},
   114  		{"gname", h.Gname},
   115  		{"devmajor", strconv.FormatInt(h.Devmajor, 10)},
   116  		{"devminor", strconv.FormatInt(h.Devminor, 10)},
   117  	}
   118  }
   119  
   120  func v1TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
   121  	// Get extended attributes.
   122  	xAttrKeys := make([]string, len(h.Xattrs))
   123  	for k := range h.Xattrs {
   124  		xAttrKeys = append(xAttrKeys, k)
   125  	}
   126  	sort.Strings(xAttrKeys)
   127  
   128  	// Make the slice with enough capacity to hold the 11 basic headers
   129  	// we want from the v0 selector plus however many xattrs we have.
   130  	orderedHeaders = make([][2]string, 0, 11+len(xAttrKeys))
   131  
   132  	// Copy all headers from v0 excluding the 'mtime' header (the 5th element).
   133  	v0headers := v0TarHeaderSelect(h)
   134  	orderedHeaders = append(orderedHeaders, v0headers[0:5]...)
   135  	orderedHeaders = append(orderedHeaders, v0headers[6:]...)
   136  
   137  	// Finally, append the sorted xattrs.
   138  	for _, k := range xAttrKeys {
   139  		orderedHeaders = append(orderedHeaders, [2]string{k, h.Xattrs[k]})
   140  	}
   141  
   142  	return
   143  }
   144  
   145  var registeredHeaderSelectors = map[Version]tarHeaderSelectFunc{
   146  	Version0:   v0TarHeaderSelect,
   147  	Version1:   v1TarHeaderSelect,
   148  	VersionDev: v1TarHeaderSelect,
   149  }
   150  
   151  func getTarHeaderSelector(v Version) (tarHeaderSelector, error) {
   152  	headerSelector, ok := registeredHeaderSelectors[v]
   153  	if !ok {
   154  		return nil, ErrVersionNotImplemented
   155  	}
   156  
   157  	return headerSelector, nil
   158  }