2014-08-21 16:12:52 -04:00
|
|
|
package tarsum
|
|
|
|
|
|
|
|
import (
|
2015-05-01 18:01:10 -04:00
|
|
|
"archive/tar"
|
2014-08-21 16:12:52 -04:00
|
|
|
"errors"
|
2014-10-30 16:47:31 -04:00
|
|
|
"sort"
|
|
|
|
"strconv"
|
2014-08-21 16:12:52 -04:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2015-07-25 04:35:07 -04:00
|
|
|
// Version is used for versioning of the TarSum algorithm
|
2014-08-21 16:12:52 -04:00
|
|
|
// based on the prefix of the hash used
|
|
|
|
// i.e. "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"
|
|
|
|
type Version int
|
|
|
|
|
2014-10-30 16:47:31 -04:00
|
|
|
// Prefix of "tarsum"
|
2014-08-21 16:12:52 -04:00
|
|
|
const (
|
|
|
|
Version0 Version = iota
|
2014-10-30 16:47:31 -04:00
|
|
|
Version1
|
2015-07-25 04:35:07 -04:00
|
|
|
// VersionDev this constant will be either the latest or an unsettled next-version of the TarSum calculation
|
2014-08-21 16:12:52 -04:00
|
|
|
VersionDev
|
|
|
|
)
|
|
|
|
|
2015-01-23 12:54:17 -05:00
|
|
|
// VersionLabelForChecksum returns the label for the given tarsum
|
|
|
|
// checksum, i.e., everything before the first `+` character in
|
|
|
|
// the string or an empty string if no label separator is found.
|
|
|
|
func VersionLabelForChecksum(checksum string) string {
|
|
|
|
// Checksums are in the form: {versionLabel}+{hashID}:{hex}
|
|
|
|
sepIndex := strings.Index(checksum, "+")
|
|
|
|
if sepIndex < 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return checksum[:sepIndex]
|
|
|
|
}
|
|
|
|
|
2015-07-25 04:35:07 -04:00
|
|
|
// GetVersions gets a list of all known tarsum versions.
|
2014-08-21 16:12:52 -04:00
|
|
|
func GetVersions() []Version {
|
|
|
|
v := []Version{}
|
|
|
|
for k := range tarSumVersions {
|
|
|
|
v = append(v, k)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2014-12-23 16:40:06 -05:00
|
|
|
var (
|
|
|
|
tarSumVersions = map[Version]string{
|
|
|
|
Version0: "tarsum",
|
|
|
|
Version1: "tarsum.v1",
|
|
|
|
VersionDev: "tarsum.dev",
|
|
|
|
}
|
|
|
|
tarSumVersionsByName = map[string]Version{
|
|
|
|
"tarsum": Version0,
|
|
|
|
"tarsum.v1": Version1,
|
|
|
|
"tarsum.dev": VersionDev,
|
|
|
|
}
|
|
|
|
)
|
2014-08-21 16:12:52 -04:00
|
|
|
|
|
|
|
func (tsv Version) String() string {
|
|
|
|
return tarSumVersions[tsv]
|
|
|
|
}
|
|
|
|
|
2015-07-25 04:35:07 -04:00
|
|
|
// GetVersionFromTarsum returns the Version from the provided string.
|
2014-08-21 16:12:52 -04:00
|
|
|
func GetVersionFromTarsum(tarsum string) (Version, error) {
|
|
|
|
tsv := tarsum
|
|
|
|
if strings.Contains(tarsum, "+") {
|
|
|
|
tsv = strings.SplitN(tarsum, "+", 2)[0]
|
|
|
|
}
|
|
|
|
for v, s := range tarSumVersions {
|
|
|
|
if s == tsv {
|
|
|
|
return v, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1, ErrNotVersion
|
|
|
|
}
|
|
|
|
|
2014-10-30 16:47:31 -04:00
|
|
|
// Errors that may be returned by functions in this package
|
2014-08-21 16:12:52 -04:00
|
|
|
var (
|
|
|
|
ErrNotVersion = errors.New("string does not include a TarSum Version")
|
|
|
|
ErrVersionNotImplemented = errors.New("TarSum Version is not yet implemented")
|
|
|
|
)
|
2014-10-30 16:47:31 -04:00
|
|
|
|
|
|
|
// tarHeaderSelector is the interface which different versions
|
|
|
|
// of tarsum should use for selecting and ordering tar headers
|
|
|
|
// for each item in the archive.
|
|
|
|
type tarHeaderSelector interface {
|
|
|
|
selectHeaders(h *tar.Header) (orderedHeaders [][2]string)
|
|
|
|
}
|
|
|
|
|
|
|
|
type tarHeaderSelectFunc func(h *tar.Header) (orderedHeaders [][2]string)
|
|
|
|
|
|
|
|
func (f tarHeaderSelectFunc) selectHeaders(h *tar.Header) (orderedHeaders [][2]string) {
|
|
|
|
return f(h)
|
|
|
|
}
|
|
|
|
|
|
|
|
func v0TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
|
|
|
|
return [][2]string{
|
|
|
|
{"name", h.Name},
|
2015-07-23 17:19:58 -04:00
|
|
|
{"mode", strconv.FormatInt(h.Mode, 10)},
|
2014-10-30 16:47:31 -04:00
|
|
|
{"uid", strconv.Itoa(h.Uid)},
|
|
|
|
{"gid", strconv.Itoa(h.Gid)},
|
2015-07-23 17:19:58 -04:00
|
|
|
{"size", strconv.FormatInt(h.Size, 10)},
|
|
|
|
{"mtime", strconv.FormatInt(h.ModTime.UTC().Unix(), 10)},
|
2014-10-30 16:47:31 -04:00
|
|
|
{"typeflag", string([]byte{h.Typeflag})},
|
|
|
|
{"linkname", h.Linkname},
|
|
|
|
{"uname", h.Uname},
|
|
|
|
{"gname", h.Gname},
|
2015-07-23 17:19:58 -04:00
|
|
|
{"devmajor", strconv.FormatInt(h.Devmajor, 10)},
|
|
|
|
{"devminor", strconv.FormatInt(h.Devminor, 10)},
|
2014-10-30 16:47:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func v1TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
|
|
|
|
// Get extended attributes.
|
|
|
|
xAttrKeys := make([]string, len(h.Xattrs))
|
|
|
|
for k := range h.Xattrs {
|
|
|
|
xAttrKeys = append(xAttrKeys, k)
|
|
|
|
}
|
|
|
|
sort.Strings(xAttrKeys)
|
|
|
|
|
|
|
|
// Make the slice with enough capacity to hold the 11 basic headers
|
|
|
|
// we want from the v0 selector plus however many xattrs we have.
|
|
|
|
orderedHeaders = make([][2]string, 0, 11+len(xAttrKeys))
|
|
|
|
|
|
|
|
// Copy all headers from v0 excluding the 'mtime' header (the 5th element).
|
|
|
|
v0headers := v0TarHeaderSelect(h)
|
|
|
|
orderedHeaders = append(orderedHeaders, v0headers[0:5]...)
|
|
|
|
orderedHeaders = append(orderedHeaders, v0headers[6:]...)
|
|
|
|
|
|
|
|
// Finally, append the sorted xattrs.
|
|
|
|
for _, k := range xAttrKeys {
|
|
|
|
orderedHeaders = append(orderedHeaders, [2]string{k, h.Xattrs[k]})
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var registeredHeaderSelectors = map[Version]tarHeaderSelectFunc{
|
|
|
|
Version0: v0TarHeaderSelect,
|
|
|
|
Version1: v1TarHeaderSelect,
|
|
|
|
VersionDev: v1TarHeaderSelect,
|
|
|
|
}
|
|
|
|
|
|
|
|
func getTarHeaderSelector(v Version) (tarHeaderSelector, error) {
|
|
|
|
headerSelector, ok := registeredHeaderSelectors[v]
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrVersionNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
return headerSelector, nil
|
|
|
|
}
|