2026-06-22 16:06:57 +02:00
|
|
|
package storage
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"reflect"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestParseFstab(t *testing.T) {
|
|
|
|
|
data := `# /etc/fstab
|
|
|
|
|
UUID=1111-2222 / ext4 defaults 0 1
|
|
|
|
|
|
|
|
|
|
/dev/sdb1 /mnt/data ext4 rw,noatime 0 2
|
|
|
|
|
LABEL=swap none swap sw
|
|
|
|
|
`
|
|
|
|
|
got := parseFstab(data)
|
|
|
|
|
want := []FstabEntry{
|
|
|
|
|
{Device: "UUID=1111-2222", Mountpoint: "/", FSType: "ext4", Options: "defaults", Dump: 0, Pass: 1},
|
|
|
|
|
{Device: "/dev/sdb1", Mountpoint: "/mnt/data", FSType: "ext4", Options: "rw,noatime", Dump: 0, Pass: 2},
|
|
|
|
|
{Device: "LABEL=swap", Mountpoint: "none", FSType: "swap", Options: "sw", Dump: 0, Pass: 0},
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
|
|
|
t.Errorf("parseFstab:\n got %+v\nwant %+v", got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestFstabSurgicalEdits(t *testing.T) {
|
|
|
|
|
path := filepath.Join(t.TempDir(), "fstab")
|
|
|
|
|
old := fstabFile
|
|
|
|
|
fstabFile = path
|
|
|
|
|
defer func() { fstabFile = old }()
|
|
|
|
|
|
|
|
|
|
// Seed with a comment + an existing entry that must survive edits.
|
|
|
|
|
seed := "# keep me\nUUID=root / ext4 defaults 0 1\n"
|
|
|
|
|
if err := os.WriteFile(path, []byte(seed), 0644); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Append a new entry.
|
|
|
|
|
if err := appendFstabLine(FstabEntry{Device: "/dev/sdb1", Mountpoint: "/mnt/data", FSType: "xfs", Options: "noatime", Pass: 2}); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
entries, err := readFstab()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if len(entries) != 2 || findEntry(entries, "/mnt/data") == nil {
|
|
|
|
|
t.Fatalf("append failed: %+v", entries)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove it again; the comment and original entry must remain.
|
|
|
|
|
removed, err := removeFstabLines("/mnt/data")
|
|
|
|
|
if err != nil || !removed {
|
|
|
|
|
t.Fatalf("remove failed: removed=%v err=%v", removed, err)
|
|
|
|
|
}
|
|
|
|
|
data, _ := os.ReadFile(path)
|
|
|
|
|
if !strings.Contains(string(data), "# keep me") || !strings.Contains(string(data), "UUID=root") {
|
|
|
|
|
t.Errorf("surgical edit clobbered other lines:\n%s", data)
|
|
|
|
|
}
|
|
|
|
|
if findEntry(mustReadFstab(t), "/mnt/data") != nil {
|
|
|
|
|
t.Error("entry still present after remove")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Removing a missing mountpoint reports not-removed.
|
|
|
|
|
if removed, _ := removeFstabLines("/nope"); removed {
|
|
|
|
|
t.Error("expected removed=false for missing mountpoint")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func mustReadFstab(t *testing.T) []FstabEntry {
|
|
|
|
|
t.Helper()
|
|
|
|
|
e, err := readFstab()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestValidateEntry(t *testing.T) {
|
2026-06-24 17:29:45 +02:00
|
|
|
for _, tt := range []struct {
|
|
|
|
|
name string
|
|
|
|
|
entry FstabEntry
|
|
|
|
|
valid bool
|
|
|
|
|
}{
|
|
|
|
|
{name: "valid", entry: FstabEntry{Device: "/dev/sdb1", Mountpoint: "/mnt/data", FSType: "ext4", Options: "defaults"}, valid: true},
|
|
|
|
|
{name: "UUID", entry: FstabEntry{Device: "UUID=ab-cd", Mountpoint: "/mnt/x", FSType: "xfs", Options: "rw,noatime"}, valid: true},
|
|
|
|
|
{name: "shell metachar in device", entry: FstabEntry{Device: "/dev/sdb1; rm -rf /", Mountpoint: "/mnt/x", FSType: "ext4", Options: "defaults"}, valid: false},
|
|
|
|
|
{name: "relative mountpoint", entry: FstabEntry{Device: "/dev/sdb1", Mountpoint: "../etc", FSType: "ext4", Options: "defaults"}, valid: false},
|
|
|
|
|
{name: "traversal in mountpoint", entry: FstabEntry{Device: "/dev/sdb1", Mountpoint: "/mnt/../../etc", FSType: "ext4", Options: "defaults"}, valid: false},
|
|
|
|
|
{name: "bad fstype", entry: FstabEntry{Device: "/dev/sdb1", Mountpoint: "/mnt/x", FSType: "ext4!", Options: "defaults"}, valid: false},
|
|
|
|
|
{name: "bad options", entry: FstabEntry{Device: "/dev/sdb1", Mountpoint: "/mnt/x", FSType: "ext4", Options: "defaults; reboot"}, valid: false},
|
|
|
|
|
} {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
err := validateEntry(tt.entry)
|
|
|
|
|
if tt.valid && err != nil {
|
|
|
|
|
t.Errorf("valid entry rejected: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !tt.valid && err == nil {
|
|
|
|
|
t.Errorf("bad entry accepted: %+v", tt.entry)
|
|
|
|
|
}
|
|
|
|
|
})
|
2026-06-22 16:06:57 +02:00
|
|
|
}
|
|
|
|
|
}
|