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 }