diff --git a/hierarchy.go b/hierarchy.go index e81ca3c..b69601a 100644 --- a/hierarchy.go +++ b/hierarchy.go @@ -35,11 +35,14 @@ func (bp byPos) Swap(i, j int) { bp[i], bp[j] = bp[j], bp[i] } // Entry is each component of content in the mtree spec file type Entry struct { - Pos int // order in the spec - Raw string // file or directory name - Name string // file or directory name - Keywords []string // TODO(vbatts) maybe a keyword typed set of values? - Type EntryType + Parent, Child *Entry // up, down + Prev, Next *Entry // left, right + Set *Entry // current `/set` for additional keywords + Pos int // order in the spec + Raw string // file or directory name + Name string // file or directory name + Keywords []string // TODO(vbatts) maybe a keyword typed set of values? + Type EntryType } func (e Entry) String() string { diff --git a/keywords.go b/keywords.go index fa08243..0be12d2 100644 --- a/keywords.go +++ b/keywords.go @@ -9,6 +9,7 @@ import ( "hash" "io" "os" + "strings" "golang.org/x/crypto/ripemd160" ) @@ -18,6 +19,53 @@ import ( // be included for the file entry. Otherwise, empty string. type KeywordFunc func(path string, info os.FileInfo) (string, error) +// KeyVal is a "keyword=value" +type KeyVal string + +// Keyword is the mapping to the available keywords +func (kv KeyVal) Keyword() string { + if !strings.Contains(string(kv), "=") { + return "" + } + chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0] + if !strings.Contains(chunks, ".") { + return chunks + } + return strings.SplitN(chunks, ".", 2)[0] +} + +// 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] +} + +// keywordSelector takes an array of "keyword=value" and filters out that only the set of words +func keywordSelector(keyval, words []string) []string { + retList := []string{} + for _, kv := range keyval { + if inSlice(KeyVal(kv).Keyword(), words) { + retList = append(retList, kv) + } + } + return retList +} + var ( // DefaultKeywords has the several default keyword producers (uid, gid, // mode, nlink, type, size, mtime) @@ -31,6 +79,7 @@ var ( "nlink", "time", } + // SetKeywords is the default set of keywords calculated for a `/set` SpecialType SetKeywords = []string{ "uid", "gid", diff --git a/walk.go b/walk.go index c598cd1..48c5e31 100644 --- a/walk.go +++ b/walk.go @@ -12,12 +12,14 @@ import ( type ExcludeFunc func(path string, info os.FileInfo) bool type dhCreator struct { - DH *DirectoryHierarchy - curSet []string - depth int - curDirNum int + DH *DirectoryHierarchy + curSet *Entry + curDir *Entry + curEnt *Entry } +var defaultSetKeywords = []string{"type=file", "nlink=1", "flags=none", "mode=0664"} + // // To be able to do a "walk" that produces an outcome with `/set ...` would // need a more linear walk, which this can not ensure. @@ -36,13 +38,13 @@ func Walk(root string, exlcudes []ExcludeFunc, keywords []string) (*DirectoryHie // handle the /set SpecialType if info.IsDir() { - if len(creator.curSet) == 0 { + if creator.curSet == nil { // set the initial /set keywords e := Entry{ Name: "/set", Type: SpecialType, Pos: len(creator.DH.Entries), - Keywords: []string{"type=file", "nlink=1", "flags=none", "mode=0664"}, + Keywords: keywordSelector(defaultSetKeywords, keywords), } for _, keyword := range SetKeywords { if str, err := KeywordFuncs[keyword](path, info); err == nil && str != "" { @@ -51,9 +53,9 @@ func Walk(root string, exlcudes []ExcludeFunc, keywords []string) (*DirectoryHie return err } } - creator.curSet = e.Keywords + creator.curSet = &e creator.DH.Entries = append(creator.DH.Entries, e) - } else if len(creator.curSet) > 0 { + } else if creator.curSet != nil { // check the attributes of the /set keywords and re-set if changed klist := []string{} for _, keyword := range SetKeywords { @@ -66,19 +68,20 @@ func Walk(root string, exlcudes []ExcludeFunc, keywords []string) (*DirectoryHie needNewSet := false for _, k := range klist { - if !inSlice(k, creator.curSet) { + if !inSlice(k, creator.curSet.Keywords) { needNewSet = true } } if needNewSet { - creator.curSet = append([]string{"type=file", "nlink=1", "flags=none", "mode=0664"}, klist...) - creator.DH.Entries = append(creator.DH.Entries, Entry{ + e := Entry{ Name: "/set", Type: SpecialType, Pos: len(creator.DH.Entries), - Keywords: creator.curSet, - }) + Keywords: append(defaultSetKeywords, klist...), + } + creator.curSet = &e + creator.DH.Entries = append(creator.DH.Entries, e) } else { creator.DH.Entries = append(creator.DH.Entries, Entry{ Type: BlankType, @@ -91,16 +94,30 @@ func Walk(root string, exlcudes []ExcludeFunc, keywords []string) (*DirectoryHie e := Entry{ Name: filepath.Base(path), Pos: len(creator.DH.Entries), + Set: creator.curSet, } for _, keyword := range keywords { if str, err := KeywordFuncs[keyword](path, info); err == nil && str != "" { - if !inSlice(str, creator.curSet) { + if !inSlice(str, creator.curSet.Keywords) { e.Keywords = append(e.Keywords, str) } } else if err != nil { return err } } + if info.IsDir() { + if creator.curDir != nil { + creator.curDir.Next = &e + } + e.Prev = creator.curDir + creator.curDir = &e + } else { + if creator.curEnt != nil { + creator.curEnt.Next = &e + } + e.Prev = creator.curEnt + creator.curEnt = &e + } creator.DH.Entries = append(creator.DH.Entries, e) return nil }) @@ -198,5 +215,4 @@ func readOrderedDirNames(dirname string) ([]string, error) { sort.Strings(names) sort.Strings(dirnames) return append(names, dirnames...), nil - } diff --git a/walk_test.go b/walk_test.go index 258bbd5..17547fa 100644 --- a/walk_test.go +++ b/walk_test.go @@ -2,6 +2,7 @@ package mtree import ( "io/ioutil" + "log" "testing" ) @@ -11,6 +12,8 @@ func TestWalk(t *testing.T) { t.Fatal(err) } + log.Fatalf("%#v", dh) + fh, err := ioutil.TempFile("", "walk.") if err != nil { t.Fatal(err)