2016-03-16 20:09:10 +00:00
|
|
|
package mtree
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2016-03-18 17:38:32 +00:00
|
|
|
"strings"
|
2017-02-15 16:10:30 +00:00
|
|
|
|
|
|
|
"github.com/vbatts/go-mtree/pkg/govis"
|
2016-03-16 20:09:10 +00:00
|
|
|
)
|
|
|
|
|
2017-02-15 16:10:30 +00:00
|
|
|
// DefaultVisFlags is the set of Vis flags used when encoding filenames and
|
|
|
|
// other similar entries.
|
|
|
|
const DefaultVisFlags govis.VisFlag = govis.VisWhite | govis.VisOctal | govis.VisGlob
|
|
|
|
|
2016-07-26 18:21:44 +00:00
|
|
|
// Keyword is the string name of a keyword, with some convenience functions for
|
|
|
|
// determining whether it is a default or bsd standard keyword.
|
|
|
|
type Keyword string
|
|
|
|
|
|
|
|
// Default returns whether this keyword is in the default set of keywords
|
|
|
|
func (k Keyword) Default() bool {
|
2016-11-18 00:47:31 +00:00
|
|
|
return InKeywordSlice(k, DefaultKeywords)
|
2016-07-26 18:21:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Bsd returns whether this keyword is in the upstream FreeBSD mtree(8)
|
|
|
|
func (k Keyword) Bsd() bool {
|
2016-11-18 00:47:31 +00:00
|
|
|
return InKeywordSlice(k, BsdKeywords)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Synonym returns the canonical name for this keyword. This is provides the
|
|
|
|
// same functionality as KeywordSynonym()
|
|
|
|
func (k Keyword) Synonym() Keyword {
|
|
|
|
return KeywordSynonym(string(k))
|
|
|
|
}
|
|
|
|
|
|
|
|
// InKeywordSlice checks for the presence of `a` in `list`
|
|
|
|
func InKeywordSlice(a Keyword, list []Keyword) bool {
|
|
|
|
for _, b := range list {
|
|
|
|
if b == a {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
func inKeyValSlice(a KeyVal, list []KeyVal) bool {
|
|
|
|
for _, b := range list {
|
|
|
|
if b == a {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToKeywords makes a list of Keyword from a list of string
|
|
|
|
func ToKeywords(list []string) []Keyword {
|
|
|
|
ret := make([]Keyword, len(list))
|
|
|
|
for i := range list {
|
|
|
|
ret[i] = Keyword(list[i])
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromKeywords makes a list of string from a list of Keyword
|
|
|
|
func FromKeywords(list []Keyword) []string {
|
|
|
|
ret := make([]string, len(list))
|
|
|
|
for i := range list {
|
|
|
|
ret[i] = string(list[i])
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// KeyValToString constructs a list of string from the list of KeyVal
|
|
|
|
func KeyValToString(list []KeyVal) []string {
|
|
|
|
ret := make([]string, len(list))
|
|
|
|
for i := range list {
|
|
|
|
ret[i] = string(list[i])
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// StringToKeyVals constructs a list of KeyVal from the list of strings, like "keyword=value"
|
|
|
|
func StringToKeyVals(list []string) []KeyVal {
|
|
|
|
ret := make([]KeyVal, len(list))
|
|
|
|
for i := range list {
|
|
|
|
ret[i] = KeyVal(list[i])
|
|
|
|
}
|
|
|
|
return ret
|
2016-07-26 18:21:44 +00:00
|
|
|
}
|
|
|
|
|
2016-03-18 17:38:32 +00:00
|
|
|
// KeyVal is a "keyword=value"
|
|
|
|
type KeyVal string
|
|
|
|
|
|
|
|
// Keyword is the mapping to the available keywords
|
2016-11-18 00:47:31 +00:00
|
|
|
func (kv KeyVal) Keyword() Keyword {
|
2016-03-18 17:38:32 +00:00
|
|
|
if !strings.Contains(string(kv), "=") {
|
2016-11-18 00:47:31 +00:00
|
|
|
return Keyword("")
|
2016-03-18 17:38:32 +00:00
|
|
|
}
|
|
|
|
chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0]
|
|
|
|
if !strings.Contains(chunks, ".") {
|
2016-11-18 00:47:31 +00:00
|
|
|
return Keyword(chunks)
|
2016-03-18 17:38:32 +00:00
|
|
|
}
|
2016-11-18 00:47:31 +00:00
|
|
|
return Keyword(strings.SplitN(chunks, ".", 2)[0])
|
2016-03-18 17:38:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// KeywordSuffix is really only used for xattr, as the keyword is a prefix to
|
|
|
|
// the xattr "namespace.key"
|
|
|
|
func (kv KeyVal) KeywordSuffix() string {
|
|
|
|
if !strings.Contains(string(kv), "=") {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0]
|
|
|
|
if !strings.Contains(chunks, ".") {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return strings.SplitN(chunks, ".", 2)[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Value is the data/value portion of "keyword=value"
|
|
|
|
func (kv KeyVal) Value() string {
|
|
|
|
if !strings.Contains(string(kv), "=") {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[1]
|
|
|
|
}
|
|
|
|
|
2017-06-22 18:53:49 +00:00
|
|
|
// NewValue returns a new KeyVal with the newval
|
|
|
|
func (kv KeyVal) NewValue(newval string) KeyVal {
|
|
|
|
if suff := kv.KeywordSuffix(); suff != "" {
|
|
|
|
return KeyVal(fmt.Sprintf("%s.%s=%s", kv.Keyword(), suff, newval))
|
|
|
|
}
|
|
|
|
return KeyVal(fmt.Sprintf("%s=%s", kv.Keyword(), newval))
|
2016-07-22 22:01:54 +00:00
|
|
|
}
|
|
|
|
|
2017-06-22 18:53:49 +00:00
|
|
|
// Equal returns whether two KeyVal are equivalent. This takes
|
2016-07-27 12:05:15 +00:00
|
|
|
// care of certain odd cases such as tar_mtime, and should be used over
|
|
|
|
// using == comparisons directly unless you really know what you're
|
|
|
|
// doing.
|
2017-06-22 18:53:49 +00:00
|
|
|
func (kv KeyVal) Equal(b KeyVal) bool {
|
2016-07-27 12:05:15 +00:00
|
|
|
// TODO: Implement handling of tar_mtime.
|
2017-06-22 18:53:49 +00:00
|
|
|
return kv.Keyword() == b.Keyword() && kv.KeywordSuffix() == b.KeywordSuffix() && kv.Value() == b.Value()
|
2016-07-27 12:05:15 +00:00
|
|
|
}
|
|
|
|
|
2016-11-18 00:47:31 +00:00
|
|
|
// keyvalSelector takes an array of KeyVal ("keyword=value") and filters out that only the set of keywords
|
|
|
|
func keyvalSelector(keyval []KeyVal, keyset []Keyword) []KeyVal {
|
|
|
|
retList := []KeyVal{}
|
2016-03-18 17:38:32 +00:00
|
|
|
for _, kv := range keyval {
|
2016-11-18 00:47:31 +00:00
|
|
|
if InKeywordSlice(kv.Keyword(), keyset) {
|
2016-03-18 17:38:32 +00:00
|
|
|
retList = append(retList, kv)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return retList
|
|
|
|
}
|
|
|
|
|
2016-11-18 00:47:31 +00:00
|
|
|
func keyValDifference(this, that []KeyVal) []KeyVal {
|
|
|
|
if len(this) == 0 {
|
|
|
|
return that
|
2016-03-23 20:58:16 +00:00
|
|
|
}
|
2016-11-18 00:47:31 +00:00
|
|
|
diff := []KeyVal{}
|
|
|
|
for _, kv := range this {
|
|
|
|
if !inKeyValSlice(kv, that) {
|
|
|
|
diff = append(diff, kv)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return diff
|
|
|
|
}
|
|
|
|
func keyValCopy(set []KeyVal) []KeyVal {
|
|
|
|
ret := make([]KeyVal, len(set))
|
|
|
|
for i := range set {
|
|
|
|
ret[i] = set[i]
|
|
|
|
}
|
|
|
|
return ret
|
2016-03-23 20:58:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Has the "keyword" present in the list of KeyVal, and returns the
|
|
|
|
// corresponding KeyVal, else an empty string.
|
2016-11-18 00:47:31 +00:00
|
|
|
func Has(keyvals []KeyVal, keyword string) KeyVal {
|
|
|
|
return HasKeyword(keyvals, Keyword(keyword))
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasKeyword the "keyword" present in the list of KeyVal, and returns the
|
|
|
|
// corresponding KeyVal, else an empty string.
|
|
|
|
func HasKeyword(keyvals []KeyVal, keyword Keyword) KeyVal {
|
|
|
|
for i := range keyvals {
|
|
|
|
if keyvals[i].Keyword() == keyword {
|
|
|
|
return keyvals[i]
|
2016-03-23 20:58:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return emptyKV
|
|
|
|
}
|
|
|
|
|
|
|
|
var emptyKV = KeyVal("")
|
|
|
|
|
|
|
|
// MergeSet takes the current setKeyVals, and then applies the entryKeyVals
|
|
|
|
// such that the entry's values win. The union is returned.
|
2016-11-18 00:47:31 +00:00
|
|
|
func MergeSet(setKeyVals, entryKeyVals []string) []KeyVal {
|
|
|
|
retList := StringToKeyVals(setKeyVals)
|
|
|
|
eKVs := StringToKeyVals(entryKeyVals)
|
|
|
|
return MergeKeyValSet(retList, eKVs)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MergeKeyValSet does a merge of the two sets of KeyVal, and the KeyVal of
|
|
|
|
// entryKeyVals win when there is a duplicate Keyword.
|
|
|
|
func MergeKeyValSet(setKeyVals, entryKeyVals []KeyVal) []KeyVal {
|
|
|
|
retList := keyValCopy(setKeyVals)
|
|
|
|
seenKeywords := []Keyword{}
|
2016-03-23 20:58:16 +00:00
|
|
|
for i := range retList {
|
|
|
|
word := retList[i].Keyword()
|
2016-11-18 00:47:31 +00:00
|
|
|
if ekv := HasKeyword(entryKeyVals, word); ekv != emptyKV {
|
2016-03-23 20:58:16 +00:00
|
|
|
retList[i] = ekv
|
|
|
|
}
|
|
|
|
seenKeywords = append(seenKeywords, word)
|
|
|
|
}
|
2016-11-18 00:47:31 +00:00
|
|
|
for i := range entryKeyVals {
|
|
|
|
if !InKeywordSlice(entryKeyVals[i].Keyword(), seenKeywords) {
|
|
|
|
retList = append(retList, entryKeyVals[i])
|
2016-03-23 20:58:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return retList
|
|
|
|
}
|
|
|
|
|
2016-03-16 20:09:10 +00:00
|
|
|
var (
|
|
|
|
// DefaultKeywords has the several default keyword producers (uid, gid,
|
|
|
|
// mode, nlink, type, size, mtime)
|
2016-11-18 00:47:31 +00:00
|
|
|
DefaultKeywords = []Keyword{
|
2016-03-16 20:09:10 +00:00
|
|
|
"size",
|
|
|
|
"type",
|
|
|
|
"uid",
|
|
|
|
"gid",
|
2016-03-17 20:29:24 +00:00
|
|
|
"mode",
|
2016-03-16 20:09:10 +00:00
|
|
|
"link",
|
|
|
|
"nlink",
|
|
|
|
"time",
|
|
|
|
}
|
2016-07-26 18:21:44 +00:00
|
|
|
|
2016-07-22 22:01:54 +00:00
|
|
|
// DefaultTarKeywords has keywords that should be used when creating a manifest from
|
|
|
|
// an archive. Currently, evaluating the # of hardlinks has not been implemented yet
|
2016-11-18 00:47:31 +00:00
|
|
|
DefaultTarKeywords = []Keyword{
|
2016-07-22 22:01:54 +00:00
|
|
|
"size",
|
|
|
|
"type",
|
|
|
|
"uid",
|
|
|
|
"gid",
|
|
|
|
"mode",
|
|
|
|
"link",
|
|
|
|
"tar_time",
|
|
|
|
}
|
2016-07-26 18:21:44 +00:00
|
|
|
|
|
|
|
// BsdKeywords is the set of keywords that is only in the upstream FreeBSD mtree
|
2016-11-18 00:47:31 +00:00
|
|
|
BsdKeywords = []Keyword{
|
2016-07-26 18:21:44 +00:00
|
|
|
"cksum",
|
2016-10-31 17:25:11 +00:00
|
|
|
"flags", // this one is really mostly BSD specific ...
|
2016-07-26 18:21:44 +00:00
|
|
|
"ignore",
|
|
|
|
"gid",
|
|
|
|
"gname",
|
|
|
|
"link",
|
|
|
|
"md5",
|
|
|
|
"md5digest",
|
|
|
|
"mode",
|
|
|
|
"nlink",
|
|
|
|
"nochange",
|
|
|
|
"optional",
|
|
|
|
"ripemd160digest",
|
|
|
|
"rmd160",
|
|
|
|
"rmd160digest",
|
|
|
|
"sha1",
|
|
|
|
"sha1digest",
|
|
|
|
"sha256",
|
|
|
|
"sha256digest",
|
|
|
|
"sha384",
|
|
|
|
"sha384digest",
|
|
|
|
"sha512",
|
|
|
|
"sha512digest",
|
|
|
|
"size",
|
|
|
|
"tags",
|
|
|
|
"time",
|
|
|
|
"type",
|
|
|
|
"uid",
|
|
|
|
"uname",
|
|
|
|
}
|
|
|
|
|
2016-03-18 17:38:32 +00:00
|
|
|
// SetKeywords is the default set of keywords calculated for a `/set` SpecialType
|
2016-11-18 00:47:31 +00:00
|
|
|
SetKeywords = []Keyword{
|
2016-03-17 20:29:24 +00:00
|
|
|
"uid",
|
|
|
|
"gid",
|
|
|
|
}
|
2016-03-16 20:09:10 +00:00
|
|
|
)
|
|
|
|
|
2016-11-16 20:42:53 +00:00
|
|
|
// KeywordSynonym returns the canonical name for keywords that have synonyms,
|
|
|
|
// and just returns the name provided if there is no synonym. In this way it
|
|
|
|
// ought to be safe to wrap any keyword name.
|
2016-11-18 00:47:31 +00:00
|
|
|
func KeywordSynonym(name string) Keyword {
|
|
|
|
var retname string
|
2016-11-16 20:42:53 +00:00
|
|
|
switch name {
|
|
|
|
case "md5":
|
2016-11-18 00:47:31 +00:00
|
|
|
retname = "md5digest"
|
2016-11-16 20:42:53 +00:00
|
|
|
case "rmd160":
|
2016-11-18 00:47:31 +00:00
|
|
|
retname = "ripemd160digest"
|
2016-11-16 20:42:53 +00:00
|
|
|
case "rmd160digest":
|
2016-11-18 00:47:31 +00:00
|
|
|
retname = "ripemd160digest"
|
2016-11-16 20:42:53 +00:00
|
|
|
case "sha1":
|
2016-11-18 00:47:31 +00:00
|
|
|
retname = "sha1digest"
|
2016-11-16 20:42:53 +00:00
|
|
|
case "sha256":
|
2016-11-18 00:47:31 +00:00
|
|
|
retname = "sha256digest"
|
2016-11-16 20:42:53 +00:00
|
|
|
case "sha384":
|
2016-11-18 00:47:31 +00:00
|
|
|
retname = "sha384digest"
|
2016-11-16 20:42:53 +00:00
|
|
|
case "sha512":
|
2016-11-18 00:47:31 +00:00
|
|
|
retname = "sha512digest"
|
2016-11-16 20:42:53 +00:00
|
|
|
case "xattrs":
|
2016-11-18 00:47:31 +00:00
|
|
|
retname = "xattr"
|
|
|
|
default:
|
|
|
|
retname = name
|
2016-11-16 20:42:53 +00:00
|
|
|
}
|
2016-11-18 00:47:31 +00:00
|
|
|
return Keyword(retname)
|
2016-11-16 20:42:53 +00:00
|
|
|
}
|