aboutsummaryrefslogtreecommitdiffhomepage
path: root/vfs/unfold.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-07-02 21:31:29 +0900
committerOphestra <cat@gensokyo.uk>2025-07-02 21:31:29 +0900
commiteec021cc4b4eb42bc9c8311755826828bfba1996 (patch)
tree5dfe107c31ee525b13af275f8261982e916c331a /vfs/unfold.go
parenta1d98823f8d17b49b432508d071b62ccd426f1ce (diff)
hakurei: move container helpers toplevel
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'vfs/unfold.go')
-rw-r--r--vfs/unfold.go107
1 files changed, 107 insertions, 0 deletions
diff --git a/vfs/unfold.go b/vfs/unfold.go
new file mode 100644
index 00000000..ca0e37b7
--- /dev/null
+++ b/vfs/unfold.go
@@ -0,0 +1,107 @@
+package vfs
+
+import (
+ "iter"
+ "path"
+ "strings"
+ "syscall"
+)
+
+// 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) }
+}
+
+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 {
+ return nil, syscall.ESTALE
+ }
+
+ 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
+}