Files
nadir-agent/internal/mounts/mounts.go
T
2026-06-22 16:06:57 +02:00

69 lines
1.9 KiB
Go

// Package mounts parses the kernel mount table (/proc/mounts). Both the system
// dashboard (disk usage) and the storage module (mount management) need it, so
// it lives here rather than being duplicated. It also exposes the octal
// unescaping that /proc/mounts and /etc/fstab use for spaces/tabs in paths.
package mounts
import (
"os"
"strings"
)
// procMounts is a var so tests can point it at a fixture.
var procMounts = "/proc/mounts"
// Mount is one mount-table line: the backing device, where it's mounted, the
// filesystem type, and the comma-separated mount options.
type Mount struct {
Device string `json:"device" example:"/dev/sda1"`
Mountpoint string `json:"mountpoint" example:"/mnt/data"`
FSType string `json:"fstype" example:"ext4"`
Options string `json:"options" example:"rw,relatime"`
}
// Proc reads and parses /proc/mounts.
func Proc() ([]Mount, error) {
data, err := os.ReadFile(procMounts)
if err != nil {
return nil, err
}
return parseProc(string(data)), nil
}
func parseProc(data string) []Mount {
entries := []Mount{}
for line := range strings.SplitSeq(data, "\n") {
f := strings.Fields(line)
if len(f) < 4 { // device mountpoint fstype options [dump pass]
continue
}
entries = append(entries, Mount{
Device: Unescape(f[0]),
Mountpoint: Unescape(f[1]),
FSType: f[2],
Options: f[3],
})
}
return entries
}
// Unescape decodes the octal escapes (\040 space, \011 tab, \012 newline,
// \134 backslash) that mount tables and fstab use for whitespace in fields.
func Unescape(s string) string {
if !strings.ContainsRune(s, '\\') {
return s
}
var b strings.Builder
for i := 0; i < len(s); i++ {
if s[i] == '\\' && i+3 < len(s) && isOctal(s[i+1]) && isOctal(s[i+2]) && isOctal(s[i+3]) {
b.WriteByte((s[i+1]-'0')*64 + (s[i+2]-'0')*8 + (s[i+3] - '0'))
i += 3
continue
}
b.WriteByte(s[i])
}
return b.String()
}
func isOctal(c byte) bool { return c >= '0' && c <= '7' }