69 lines
1.9 KiB
Go
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' }
|