versioning.go 4.8 KB

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