package tarsum import ( "archive/tar" "errors" "sort" "strconv" "strings" ) // Version is used for versioning of the TarSum algorithm // based on the prefix of the hash used // i.e. "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b" type Version int // Prefix of "tarsum" const ( Version0 Version = iota Version1 // VersionDev this constant will be either the latest or an unsettled next-version of the TarSum calculation VersionDev ) // 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] } // GetVersions gets a list of all known tarsum versions. func GetVersions() []Version { v := []Version{} for k := range tarSumVersions { v = append(v, k) } return v } 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, } ) func (tsv Version) String() string { return tarSumVersions[tsv] } // GetVersionFromTarsum returns the Version from the provided string. 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 } // Errors that may be returned by functions in this package var ( ErrNotVersion = errors.New("string does not include a TarSum Version") ErrVersionNotImplemented = errors.New("TarSum Version is not yet implemented") ) // 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}, {"mode", strconv.FormatInt(h.Mode, 10)}, {"uid", strconv.Itoa(h.Uid)}, {"gid", strconv.Itoa(h.Gid)}, {"size", strconv.FormatInt(h.Size, 10)}, {"mtime", strconv.FormatInt(h.ModTime.UTC().Unix(), 10)}, {"typeflag", string([]byte{h.Typeflag})}, {"linkname", h.Linkname}, {"uname", h.Uname}, {"gname", h.Gname}, {"devmajor", strconv.FormatInt(h.Devmajor, 10)}, {"devminor", strconv.FormatInt(h.Devminor, 10)}, } } 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 }