File: update.go

package info (click to toggle)
golang-github-vbatts-go-mtree 0.4.4-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bookworm-backports, bullseye, sid, trixie
  • size: 776 kB
  • sloc: sh: 150; makefile: 68
file content (154 lines) | stat: -rw-r--r-- 3,865 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package mtree

import (
	"container/heap"
	"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 os.Chdir(curDir)
	}

	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
}