2016-08-02 19:57:51 +00:00
|
|
|
package mtree
|
|
|
|
|
|
|
|
import (
|
2017-06-26 19:12:31 +00:00
|
|
|
"container/heap"
|
2023-10-24 01:45:19 +00:00
|
|
|
"fmt"
|
2016-08-02 19:57:51 +00:00
|
|
|
"os"
|
|
|
|
"sort"
|
2017-06-15 18:54:58 +00:00
|
|
|
|
2017-11-03 15:10:58 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2016-08-02 19:57:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// DefaultUpdateKeywords is the default set of keywords that can take updates to the files on disk
|
|
|
|
var DefaultUpdateKeywords = []Keyword{
|
|
|
|
"uid",
|
|
|
|
"gid",
|
|
|
|
"mode",
|
2017-06-15 18:54:58 +00:00
|
|
|
"xattr",
|
2017-06-26 19:12:31 +00:00
|
|
|
"link",
|
|
|
|
"time",
|
2016-08-02 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2023-10-24 01:45:19 +00:00
|
|
|
defer func() {
|
|
|
|
err := os.Chdir(curDir)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warn(fmt.Errorf("failed to change directory to %q: %w", curDir, err))
|
|
|
|
}
|
|
|
|
}()
|
2016-08-02 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.Chdir(root); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
sort.Sort(byPos(creator.DH.Entries))
|
|
|
|
|
2017-06-26 19:12:31 +00:00
|
|
|
// This is for deferring the update of mtimes of directories, to unwind them
|
|
|
|
// in a most specific path first
|
|
|
|
h := &pathUpdateHeap{}
|
|
|
|
heap.Init(h)
|
|
|
|
|
2016-08-02 19:57:51 +00:00
|
|
|
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
|
|
|
|
}
|
2017-06-15 18:54:58 +00:00
|
|
|
logrus.Debugf("%#v", e)
|
2016-08-02 19:57:51 +00:00
|
|
|
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:
|
2017-06-15 18:54:58 +00:00
|
|
|
var kvToUpdate []KeyVal
|
|
|
|
kvToUpdate = keyvalSelector(e.AllKeys(), keywords)
|
|
|
|
logrus.Debugf("kvToUpdate(%q): %#v", pathname, kvToUpdate)
|
2016-08-02 19:57:51 +00:00
|
|
|
|
2017-06-15 18:54:58 +00:00
|
|
|
for _, kv := range kvToUpdate {
|
|
|
|
if !InKeywordSlice(kv.Keyword().Prefix(), keywordPrefixes(keywords)) {
|
2016-08-02 19:57:51 +00:00
|
|
|
continue
|
|
|
|
}
|
2017-06-15 18:54:58 +00:00
|
|
|
logrus.Debugf("finding function for %q (%q)", kv.Keyword(), kv.Keyword().Prefix())
|
|
|
|
ukFunc, ok := UpdateKeywordFuncs[kv.Keyword().Prefix()]
|
2016-08-02 19:57:51 +00:00
|
|
|
if !ok {
|
2017-06-15 18:54:58 +00:00
|
|
|
logrus.Debugf("no UpdateKeywordFunc for %s; skipping", kv.Keyword())
|
2016-08-02 19:57:51 +00:00
|
|
|
continue
|
|
|
|
}
|
2017-06-26 19:12:31 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2017-06-26 18:24:10 +00:00
|
|
|
if _, err := ukFunc(pathname, kv); err != nil {
|
2016-08-02 19:57:51 +00:00
|
|
|
results = append(results, InodeDelta{
|
|
|
|
diff: ErrorDifference,
|
|
|
|
path: pathname,
|
|
|
|
old: e,
|
|
|
|
keys: []KeyDelta{
|
|
|
|
{
|
|
|
|
diff: ErrorDifference,
|
|
|
|
name: kv.Keyword(),
|
|
|
|
err: err,
|
|
|
|
},
|
|
|
|
}})
|
|
|
|
}
|
2017-06-26 19:12:31 +00:00
|
|
|
// 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.
|
2016-08-02 19:57:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-26 19:12:31 +00:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
}})
|
|
|
|
}
|
|
|
|
}
|
2016-08-02 19:57:51 +00:00
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
2017-06-26 19:12:31 +00:00
|
|
|
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
|
2016-08-02 19:57:51 +00:00
|
|
|
}
|