versioning.go 4.2 KB

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