aboutsummaryrefslogtreecommitdiffhomepage
path: root/vfs/unfold.go
blob: 37c4cef3147a9b6c491033cf496a3e043f09f4ac (plain)
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
package vfs

import (
	"iter"
	"path"
	"strings"
)

// UnfoldTargetError is a pathname that never appeared in a mount hierarchy.
type UnfoldTargetError string

func (e UnfoldTargetError) Error() string {
	return "mount point " + string(e) + " never appeared in mountinfo"
}

// MountInfoNode positions a [MountInfoEntry] in its mount hierarchy.
type MountInfoNode struct {
	*MountInfoEntry
	FirstChild  *MountInfoNode `json:"first_child"`
	NextSibling *MountInfoNode `json:"next_sibling"`

	Clean   string `json:"clean"`
	Covered bool   `json:"covered"`
}

// Collective returns an iterator over visible mountinfo nodes.
func (n *MountInfoNode) Collective() iter.Seq[*MountInfoNode] {
	return func(yield func(*MountInfoNode) bool) { n.visit(yield) }
}

// visit recursively visits all visible mountinfo nodes.
func (n *MountInfoNode) visit(yield func(*MountInfoNode) bool) bool {
	if !n.Covered && !yield(n) {
		return false
	}
	for cur := n.FirstChild; cur != nil; cur = cur.NextSibling {
		if !cur.visit(yield) {
			return false
		}
	}
	return true
}

// Unfold unfolds the mount hierarchy and resolves covered paths.
func (d *MountInfoDecoder) Unfold(target string) (*MountInfoNode, error) {
	targetClean := path.Clean(target)

	var mountinfoSize int
	for range d.Entries() {
		mountinfoSize++
	}
	if err := d.Err(); err != nil {
		return nil, err
	}

	mountinfo := make([]*MountInfoNode, mountinfoSize)
	// mount ID to index lookup
	idIndex := make(map[int]int, mountinfoSize)
	// final entry to match target
	targetIndex := -1
	{
		i := 0
		for ent := range d.Entries() {
			mountinfo[i] = &MountInfoNode{Clean: path.Clean(ent.Target), MountInfoEntry: ent}
			idIndex[ent.ID] = i
			if mountinfo[i].Clean == targetClean {
				targetIndex = i
			}

			i++
		}
	}

	if targetIndex == -1 {
		// target does not exist in parsed mountinfo
		return nil, &DecoderError{Op: "unfold", Line: -1, Err: UnfoldTargetError(targetClean)}
	}

	for _, cur := range mountinfo {
		var parent *MountInfoNode
		if p, ok := idIndex[cur.Parent]; !ok {
			continue
		} else {
			parent = mountinfo[p]
		}

		if !strings.HasPrefix(cur.Clean, targetClean) {
			continue
		}
		if parent.Clean == cur.Clean {
			parent.Covered = true
		}

		covered := false
		nsp := &parent.FirstChild
		for s := parent.FirstChild; s != nil; s = s.NextSibling {
			if strings.HasPrefix(cur.Clean, s.Clean) {
				covered = true
				break
			}

			if strings.HasPrefix(s.Clean, cur.Clean) {
				*nsp = s.NextSibling
			} else {
				nsp = &s.NextSibling
			}
		}
		if covered {
			continue
		}
		*nsp = cur
	}

	return mountinfo[targetIndex], nil
}