forked from rosa/hakurei
This makes package check portable, and removes nonportable behaviour from package pkg, pipewire, and system. All other packages remain nonportable due to their nature. No latency increase was observed due to this change on amd64 and arm64 linux. Signed-off-by: Ophestra <cat@gensokyo.uk>
116 lines
2.6 KiB
Go
116 lines
2.6 KiB
Go
package vfs
|
|
|
|
import (
|
|
"iter"
|
|
"path/filepath"
|
|
"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 := filepath.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: filepath.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
|
|
}
|