package mtree import ( "container/heap" "fmt" "os" "sort" "github.com/sirupsen/logrus" ) // DefaultUpdateKeywords is the default set of keywords that can take updates to the files on disk var DefaultUpdateKeywords = []Keyword{ "uid", "gid", "mode", "xattr", "link", "time", } // Update attempts to set the attributes of root directory path, given the values of `keywords` in dh DirectoryHierarchy. func Update(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval) ([]InodeDelta, error) { creator := dhCreator{DH: dh} curDir, err := os.Getwd() if err == nil { defer func() { err := os.Chdir(curDir) if err != nil { logrus.Warn(fmt.Errorf("failed to change directory to %q: %w", curDir, err)) } }() } if err := os.Chdir(root); err != nil { return nil, err } sort.Sort(byPos(creator.DH.Entries)) // This is for deferring the update of mtimes of directories, to unwind them // in a most specific path first h := &pathUpdateHeap{} heap.Init(h) results := []InodeDelta{} for i, e := range creator.DH.Entries { switch e.Type { case SpecialType: if e.Name == "/set" { creator.curSet = &creator.DH.Entries[i] } else if e.Name == "/unset" { creator.curSet = nil } logrus.Debugf("%#v", e) continue case RelativeType, FullType: e.Set = creator.curSet pathname, err := e.Path() if err != nil { return nil, err } // filter the keywords to update on the file, from the keywords available for this entry: var kvToUpdate []KeyVal kvToUpdate = keyvalSelector(e.AllKeys(), keywords) logrus.Debugf("kvToUpdate(%q): %#v", pathname, kvToUpdate) for _, kv := range kvToUpdate { if !InKeywordSlice(kv.Keyword().Prefix(), keywordPrefixes(keywords)) { continue } logrus.Debugf("finding function for %q (%q)", kv.Keyword(), kv.Keyword().Prefix()) ukFunc, ok := UpdateKeywordFuncs[kv.Keyword().Prefix()] if !ok { logrus.Debugf("no UpdateKeywordFunc for %s; skipping", kv.Keyword()) continue } // TODO check for the type=dir of the entry as well if kv.Keyword().Prefix() == "time" && e.IsDir() { heap.Push(h, pathUpdate{ Path: pathname, E: e, KV: kv, Func: ukFunc, }) continue } if _, err := ukFunc(pathname, kv); err != nil { results = append(results, InodeDelta{ diff: ErrorDifference, path: pathname, old: e, keys: []KeyDelta{ { diff: ErrorDifference, name: kv.Keyword(), err: err, }, }}) } // XXX really would be great to have a Check() or Compare() right here, // to compare each entry as it is encountered, rather than just running // Check() on this path after the whole update is finished. } } } for h.Len() > 0 { pu := heap.Pop(h).(pathUpdate) if _, err := pu.Func(pu.Path, pu.KV); err != nil { results = append(results, InodeDelta{ diff: ErrorDifference, path: pu.Path, old: pu.E, keys: []KeyDelta{ { diff: ErrorDifference, name: pu.KV.Keyword(), err: err, }, }}) } } return results, nil } type pathUpdateHeap []pathUpdate func (h pathUpdateHeap) Len() int { return len(h) } func (h pathUpdateHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } // This may end up looking backwards, but for container/heap, Less evaluates // the negative priority. So when popping members of the array, it will be // sorted by least. For this use-case, we want the most-qualified-name popped // first (the longest path name), such that "." is the last entry popped. func (h pathUpdateHeap) Less(i, j int) bool { return len(h[i].Path) > len(h[j].Path) } func (h *pathUpdateHeap) Push(x interface{}) { *h = append(*h, x.(pathUpdate)) } func (h *pathUpdateHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] *h = old[0 : n-1] return x } type pathUpdate struct { Path string E Entry KV KeyVal Func UpdateKeywordFunc }