package roothelper

import (
	"os"
	"path/filepath"
	"testing"

	"banger/internal/daemon/dmsnap"
	"banger/internal/firecracker"
	"banger/internal/paths"
)

func TestValidateDMDevicePath(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		name string
		path string
		ok   bool
	}{
		{name: "valid", path: "/dev/mapper/fc-rootfs-test", ok: true},
		{name: "wrong_prefix", path: "/dev/mapper/not-banger", ok: false},
		{name: "wrong_dir", path: "/tmp/fc-rootfs-test", ok: false},
		{name: "relative", path: "fc-rootfs-test", ok: false},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			err := validateDMDevicePath(tc.path)
			if tc.ok && err != nil {
				t.Fatalf("validateDMDevicePath(%q) = %v, want nil", tc.path, err)
			}
			if !tc.ok && err == nil {
				t.Fatalf("validateDMDevicePath(%q) succeeded, want error", tc.path)
			}
		})
	}
}

func TestValidateFirecrackerPID(t *testing.T) {
	t.Parallel()

	if err := validateFirecrackerPID(0); err == nil {
		t.Fatal("validateFirecrackerPID(0) succeeded, want error")
	}
	if err := validateFirecrackerPID(-1); err == nil {
		t.Fatal("validateFirecrackerPID(-1) succeeded, want error")
	}
	// Self pid points at the go test binary, whose cmdline does not
	// contain "firecracker" — rejection proves the helper would refuse
	// to kill arbitrary host processes.
	if err := validateFirecrackerPID(os.Getpid()); err == nil {
		t.Fatal("validateFirecrackerPID(test pid) succeeded, want error")
	}
	// PID 1 is init/systemd on Linux — a juicy target for a compromised
	// daemon, and definitely not firecracker. Make sure we'd refuse.
	if err := validateFirecrackerPID(1); err == nil {
		t.Fatal("validateFirecrackerPID(1) succeeded, want error")
	}
}

// TestValidateRootExecutableRejectsSymlink pins the O_NOFOLLOW
// guarantee: even if the path string passes a textual check, a symlink
// at the leaf is refused before we ever stat the target.
func TestValidateRootExecutableRejectsSymlink(t *testing.T) {
	t.Parallel()
	dir := t.TempDir()
	regular := filepath.Join(dir, "real")
	if err := os.WriteFile(regular, []byte{}, 0o755); err != nil {
		t.Fatalf("write regular: %v", err)
	}
	link := filepath.Join(dir, "link")
	if err := os.Symlink(regular, link); err != nil {
		t.Fatalf("symlink: %v", err)
	}
	if err := validateRootExecutable(link); err == nil {
		t.Fatal("validateRootExecutable(symlink) succeeded, want error")
	}
}

// TestValidateRootExecutableRejectsNonRootOwned exercises the Fstat
// uid check on a file the test user just created: it can't possibly
// be uid 0, so the validator must refuse it. This is the regression
// guard against the previous os.Stat code path drifting back in.
func TestValidateRootExecutableRejectsNonRootOwned(t *testing.T) {
	t.Parallel()
	if os.Getuid() == 0 {
		t.Skip("test runs as root; cannot construct a non-root-owned file in a tempdir we can write")
	}
	path := filepath.Join(t.TempDir(), "binary")
	if err := os.WriteFile(path, []byte{}, 0o755); err != nil {
		t.Fatalf("write: %v", err)
	}
	err := validateRootExecutable(path)
	if err == nil {
		t.Fatal("validateRootExecutable(user-owned) succeeded, want error")
	}
	if !contains(err.Error(), "root-owned") {
		t.Fatalf("err = %v, want root-owned rejection", err)
	}
}

func TestValidateRootExecutableRejectsGroupWritable(t *testing.T) {
	t.Parallel()
	if os.Getuid() == 0 {
		t.Skip("test runs as root; can't construct a non-root-owned file")
	}
	path := filepath.Join(t.TempDir(), "binary")
	if err := os.WriteFile(path, []byte{}, 0o775); err != nil {
		t.Fatalf("write: %v", err)
	}
	err := validateRootExecutable(path)
	if err == nil {
		t.Fatal("validateRootExecutable(group-writable) succeeded, want error")
	}
}

// contains is a local substring helper that mirrors strings.Contains
// without pulling in the package — kept tiny so the test file's
// dependency surface stays close to the thing being tested.
func contains(s, sub string) bool {
	for i := 0; i+len(sub) <= len(s); i++ {
		if s[i:i+len(sub)] == sub {
			return true
		}
	}
	return false
}

func TestValidateSignalName(t *testing.T) {
	t.Parallel()
	for _, tc := range []struct {
		name string
		arg  string
		ok   bool
	}{
		{name: "TERM", arg: "TERM", ok: true},
		{name: "SIGTERM", arg: "SIGTERM", ok: true},
		{name: "lowercase_kill", arg: "kill", ok: true},
		{name: "with_whitespace", arg: " HUP ", ok: true},
		{name: "USR1", arg: "USR1", ok: true},
		{name: "ABRT", arg: "ABRT", ok: true},
		{name: "empty", arg: "", ok: false},
		{name: "numeric_9", arg: "9", ok: false},
		{name: "STOP_DoS", arg: "STOP", ok: false},
		{name: "CONT", arg: "CONT", ok: false},
		{name: "realtime", arg: "RTMIN+1", ok: false},
		{name: "garbage", arg: "FOOBAR", ok: false},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			err := validateSignalName(tc.arg)
			if tc.ok && err != nil {
				t.Fatalf("validateSignalName(%q) = %v, want nil", tc.arg, err)
			}
			if !tc.ok && err == nil {
				t.Fatalf("validateSignalName(%q) succeeded, want error", tc.arg)
			}
		})
	}
}

func TestExtractFirecrackerAPISock(t *testing.T) {
	t.Parallel()
	for _, tc := range []struct {
		name    string
		cmdline string
		want    string
	}{
		{name: "long_form_space", cmdline: "firecracker --api-sock /run/banger/fc-abc.sock --id abc", want: "/run/banger/fc-abc.sock"},
		{name: "long_form_equals", cmdline: "firecracker --api-sock=/run/banger/fc-abc.sock --id abc", want: "/run/banger/fc-abc.sock"},
		{name: "short_form", cmdline: "firecracker -a /run/banger/fc-abc.sock --id abc", want: "/run/banger/fc-abc.sock"},
		{name: "absent", cmdline: "firecracker --id abc", want: ""},
		{name: "trailing_flag", cmdline: "firecracker --api-sock", want: ""},
		{name: "empty", cmdline: "", want: ""},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			got := extractFirecrackerAPISock(tc.cmdline)
			if got != tc.want {
				t.Fatalf("extractFirecrackerAPISock(%q) = %q, want %q", tc.cmdline, got, tc.want)
			}
		})
	}
}

func TestPathIsUnder(t *testing.T) {
	t.Parallel()
	for _, tc := range []struct {
		name string
		p    string
		root string
		want bool
	}{
		{name: "exact", p: "/var/lib/banger", root: "/var/lib/banger", want: true},
		{name: "nested", p: "/var/lib/banger/jail/x", root: "/var/lib/banger", want: true},
		{name: "sibling", p: "/var/lib/banger-other", root: "/var/lib/banger", want: false},
		{name: "outside", p: "/etc/passwd", root: "/var/lib/banger", want: false},
		{name: "empty_root", p: "/anywhere", root: "", want: false},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if got := pathIsUnder(tc.p, tc.root); got != tc.want {
				t.Fatalf("pathIsUnder(%q, %q) = %v, want %v", tc.p, tc.root, got, tc.want)
			}
		})
	}
}

func TestValidateLoopDevicePath(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		name string
		arg  string
		ok   bool
	}{
		{name: "loop0", arg: "/dev/loop0", ok: true},
		{name: "loop12", arg: "/dev/loop12", ok: true},
		{name: "no_index", arg: "/dev/loop", ok: false},
		{name: "non_numeric", arg: "/dev/loop-x", ok: false},
		{name: "wrong_prefix", arg: "/dev/sda1", ok: false},
		{name: "empty", arg: "", ok: false},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			err := validateLoopDevicePath(tc.arg)
			if tc.ok && err != nil {
				t.Fatalf("validateLoopDevicePath(%q) = %v, want nil", tc.arg, err)
			}
			if !tc.ok && err == nil {
				t.Fatalf("validateLoopDevicePath(%q) succeeded, want error", tc.arg)
			}
		})
	}
}

func TestValidateDMRemoveTarget(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		name string
		arg  string
		ok   bool
	}{
		{name: "dm_name", arg: "fc-rootfs-abc", ok: true},
		{name: "dm_device_path", arg: "/dev/mapper/fc-rootfs-abc", ok: true},
		{name: "wrong_prefix", arg: "not-banger", ok: false},
		{name: "device_wrong_prefix", arg: "/dev/mapper/not-banger", ok: false},
		{name: "empty", arg: "", ok: false},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			err := validateDMRemoveTarget(tc.arg)
			if tc.ok && err != nil {
				t.Fatalf("validateDMRemoveTarget(%q) = %v, want nil", tc.arg, err)
			}
			if !tc.ok && err == nil {
				t.Fatalf("validateDMRemoveTarget(%q) succeeded, want error", tc.arg)
			}
		})
	}
}

func TestValidateDMSnapshotHandles(t *testing.T) {
	t.Parallel()

	// Empty handles are tolerated — the dmsnap layer treats every
	// missing field as a no-op for that step.
	if err := validateDMSnapshotHandles(dmsnap.Handles{}); err != nil {
		t.Fatalf("validateDMSnapshotHandles(empty) = %v, want nil", err)
	}
	good := dmsnap.Handles{
		BaseLoop: "/dev/loop0",
		COWLoop:  "/dev/loop1",
		DMName:   "fc-rootfs-abc",
		DMDev:    "/dev/mapper/fc-rootfs-abc",
	}
	if err := validateDMSnapshotHandles(good); err != nil {
		t.Fatalf("validateDMSnapshotHandles(good) = %v, want nil", err)
	}
	for _, tc := range []struct {
		name    string
		mutate  func(dmsnap.Handles) dmsnap.Handles
		wantErr bool
	}{
		{name: "bad_dm_name", mutate: func(h dmsnap.Handles) dmsnap.Handles {
			h.DMName = "rogue"
			return h
		}, wantErr: true},
		{name: "bad_dm_device", mutate: func(h dmsnap.Handles) dmsnap.Handles {
			h.DMDev = "/dev/sda1"
			return h
		}, wantErr: true},
		{name: "bad_base_loop", mutate: func(h dmsnap.Handles) dmsnap.Handles {
			h.BaseLoop = "/dev/sda1"
			return h
		}, wantErr: true},
		{name: "bad_cow_loop", mutate: func(h dmsnap.Handles) dmsnap.Handles {
			h.COWLoop = "/etc/shadow"
			return h
		}, wantErr: true},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			err := validateDMSnapshotHandles(tc.mutate(good))
			if tc.wantErr && err == nil {
				t.Fatalf("validateDMSnapshotHandles(%s) succeeded, want error", tc.name)
			}
			if !tc.wantErr && err != nil {
				t.Fatalf("validateDMSnapshotHandles(%s) = %v, want nil", tc.name, err)
			}
		})
	}
}

// TestValidateManagedPathRejectsSymlinkLeaf pins the leaf-symlink
// rejection: even when the path string sits inside a managed root, a
// symlink at the final component must be refused. Otherwise a
// daemon-UID attacker could plant `<StateDir>/foo -> /etc/shadow` and
// get the helper to drive privileged tooling against host files.
func TestValidateManagedPathRejectsSymlinkLeaf(t *testing.T) {
	t.Parallel()
	srv := &Server{}
	root := t.TempDir()
	target := filepath.Join(t.TempDir(), "outside")
	if err := os.WriteFile(target, []byte("secret"), 0o600); err != nil {
		t.Fatalf("write target: %v", err)
	}
	link := filepath.Join(root, "leak")
	if err := os.Symlink(target, link); err != nil {
		t.Fatalf("symlink: %v", err)
	}
	err := srv.validateManagedPath(link, root)
	if err == nil {
		t.Fatal("validateManagedPath(symlink leaf) succeeded, want error")
	}
}

// TestValidateManagedPathRejectsSymlinkIntermediate pins ancestor
// symlink rejection. Without the walk, an attacker plants
// `<StateDir>/dir -> /etc` and a path like `<StateDir>/dir/passwd`
// passes the textual prefix check but resolves to /etc/passwd at op
// time.
func TestValidateManagedPathRejectsSymlinkIntermediate(t *testing.T) {
	t.Parallel()
	srv := &Server{}
	root := t.TempDir()
	target := t.TempDir()
	link := filepath.Join(root, "redirect")
	if err := os.Symlink(target, link); err != nil {
		t.Fatalf("symlink: %v", err)
	}
	err := srv.validateManagedPath(filepath.Join(link, "passwd"), root)
	if err == nil {
		t.Fatal("validateManagedPath(symlink intermediate) succeeded, want error")
	}
}

// TestValidateManagedPathToleratesMissingLeaf confirms ENOENT does
// not flip the validator into a fail. Several callers pass paths
// firecracker (or the helper's own staging) creates AFTER validation
// — sockets, log files, kernel hard-link targets — and a strict
// existence check would break those flows.
func TestValidateManagedPathToleratesMissingLeaf(t *testing.T) {
	t.Parallel()
	srv := &Server{}
	root := t.TempDir()
	missing := filepath.Join(root, "deeper", "not-yet")
	if err := srv.validateManagedPath(missing, root); err != nil {
		t.Fatalf("validateManagedPath(missing leaf) = %v, want nil", err)
	}
}

// TestValidateManagedPathPassesPlainSubpath is the happy path: a
// regular file inside a real subdir should sail through the new walk.
func TestValidateManagedPathPassesPlainSubpath(t *testing.T) {
	t.Parallel()
	srv := &Server{}
	root := t.TempDir()
	subdir := filepath.Join(root, "vms", "abc")
	if err := os.MkdirAll(subdir, 0o755); err != nil {
		t.Fatalf("mkdir: %v", err)
	}
	leaf := filepath.Join(subdir, "rootfs.ext4")
	if err := os.WriteFile(leaf, []byte("data"), 0o644); err != nil {
		t.Fatalf("write leaf: %v", err)
	}
	if err := srv.validateManagedPath(leaf, root); err != nil {
		t.Fatalf("validateManagedPath(plain subpath) = %v, want nil", err)
	}
}

func TestValidateBangerBridgeName(t *testing.T) {
	t.Parallel()
	for _, tc := range []struct {
		name string
		arg  string
		ok   bool
	}{
		{name: "default", arg: "br-fc", ok: true},
		{name: "suffixed", arg: "br-fc-alt", ok: true},
		{name: "with_whitespace", arg: " br-fc ", ok: true},
		{name: "wrong_prefix", arg: "br0", ok: false},
		{name: "host_iface", arg: "eth0", ok: false},
		{name: "docker", arg: "docker0", ok: false},
		{name: "loopback", arg: "lo", ok: false},
		{name: "empty", arg: "", ok: false},
		{name: "br_dash_only", arg: "br-", ok: false}, // not "br-fc" exactly
		{name: "almost_match", arg: "br-fcx", ok: false},
		{name: "with_slash", arg: "br-fc/x", ok: false},
		{name: "too_long", arg: "br-fc-aaaaaaaaaa", ok: false}, // 16 chars
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			err := validateBangerBridgeName(tc.arg)
			if tc.ok && err != nil {
				t.Fatalf("validateBangerBridgeName(%q) = %v, want nil", tc.arg, err)
			}
			if !tc.ok && err == nil {
				t.Fatalf("validateBangerBridgeName(%q) succeeded, want error", tc.arg)
			}
		})
	}
}

func TestValidateCIDRPrefix(t *testing.T) {
	t.Parallel()
	for _, tc := range []struct {
		name string
		arg  string
		ok   bool
	}{
		{name: "default_24", arg: "24", ok: true},
		{name: "min_8", arg: "8", ok: true},
		{name: "max_32", arg: "32", ok: true},
		{name: "with_whitespace", arg: " 16 ", ok: true},
		{name: "below_min", arg: "7", ok: false},
		{name: "above_max", arg: "33", ok: false},
		{name: "non_numeric", arg: "abc", ok: false},
		{name: "ipv6_prefix", arg: "64", ok: false}, // outside [8, 32]
		{name: "with_slash", arg: "/24", ok: false},
		{name: "empty", arg: "", ok: false},
		{name: "negative", arg: "-1", ok: false},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			err := validateCIDRPrefix(tc.arg)
			if tc.ok && err != nil {
				t.Fatalf("validateCIDRPrefix(%q) = %v, want nil", tc.arg, err)
			}
			if !tc.ok && err == nil {
				t.Fatalf("validateCIDRPrefix(%q) succeeded, want error", tc.arg)
			}
		})
	}
}

func TestValidateNetworkConfig(t *testing.T) {
	t.Parallel()
	good := NetworkConfig{
		BridgeName: "br-fc",
		BridgeIP:   "172.16.0.1",
		CIDR:       "24",
	}
	if err := validateNetworkConfig(good); err != nil {
		t.Fatalf("validateNetworkConfig(default) = %v, want nil", err)
	}
	for _, tc := range []struct {
		name   string
		mutate func(NetworkConfig) NetworkConfig
	}{
		{name: "bad_bridge", mutate: func(c NetworkConfig) NetworkConfig { c.BridgeName = "eth0"; return c }},
		{name: "bad_ip", mutate: func(c NetworkConfig) NetworkConfig { c.BridgeIP = "::1"; return c }},
		{name: "bad_cidr", mutate: func(c NetworkConfig) NetworkConfig { c.CIDR = "/24"; return c }},
		{name: "missing_ip", mutate: func(c NetworkConfig) NetworkConfig { c.BridgeIP = ""; return c }},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if err := validateNetworkConfig(tc.mutate(good)); err == nil {
				t.Fatalf("validateNetworkConfig(%s) succeeded, want error", tc.name)
			}
		})
	}
}

func TestValidateLinuxIfaceName(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		name string
		arg  string
		ok   bool
	}{
		{name: "typical_bridge", arg: "br-banger", ok: true},
		{name: "uplink", arg: "enp5s0", ok: true},
		{name: "max_len", arg: "a234567890abcde", ok: true}, // 15 chars
		{name: "empty", arg: "", ok: false},
		{name: "too_long", arg: "a234567890abcdef", ok: false},
		{name: "with_slash", arg: "br/0", ok: false},
		{name: "with_space", arg: "br 0", ok: false},
		{name: "with_colon", arg: "br:0", ok: false},
		{name: "dot", arg: ".", ok: false},
		{name: "dotdot", arg: "..", ok: false},
		{name: "control_char", arg: "br\x01", ok: false},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			err := validateLinuxIfaceName(tc.arg)
			if tc.ok && err != nil {
				t.Fatalf("validateLinuxIfaceName(%q) = %v, want nil", tc.arg, err)
			}
			if !tc.ok && err == nil {
				t.Fatalf("validateLinuxIfaceName(%q) succeeded, want error", tc.arg)
			}
		})
	}
}

func TestValidateIPv4(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		name string
		arg  string
		ok   bool
	}{
		{name: "valid", arg: "172.16.0.2", ok: true},
		{name: "with_whitespace", arg: " 10.0.0.1 ", ok: true},
		{name: "empty", arg: "", ok: false},
		{name: "ipv6", arg: "::1", ok: false},
		{name: "garbage", arg: "not-an-ip", ok: false},
		{name: "with_cidr", arg: "10.0.0.1/24", ok: false},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			err := validateIPv4(tc.arg)
			if tc.ok && err != nil {
				t.Fatalf("validateIPv4(%q) = %v, want nil", tc.arg, err)
			}
			if !tc.ok && err == nil {
				t.Fatalf("validateIPv4(%q) succeeded, want error", tc.arg)
			}
		})
	}
}

func TestValidateResolverAddr(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		name string
		arg  string
		ok   bool
	}{
		{name: "ipv4", arg: "192.168.1.1", ok: true},
		{name: "ipv6", arg: "fe80::1", ok: true},
		{name: "ipv4_with_port", arg: "127.0.0.1:42069", ok: true},
		{name: "ipv6_with_port", arg: "[fe80::1]:42069", ok: true},
		{name: "empty", arg: "", ok: false},
		{name: "garbage", arg: "resolver.example", ok: false},
		{name: "garbage_with_port", arg: "resolver.example:53", ok: false},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			err := validateResolverAddr(tc.arg)
			if tc.ok && err != nil {
				t.Fatalf("validateResolverAddr(%q) = %v, want nil", tc.arg, err)
			}
			if !tc.ok && err == nil {
				t.Fatalf("validateResolverAddr(%q) succeeded, want error", tc.arg)
			}
		})
	}
}

func TestValidateExt4ImagePath(t *testing.T) {
	t.Parallel()

	srv := &Server{}
	stateDir := paths.ResolveSystem().StateDir
	for _, tc := range []struct {
		name string
		arg  string
		ok   bool
	}{
		{name: "managed_image", arg: filepath.Join(stateDir, "vms", "abc", "rootfs.ext4"), ok: true},
		{name: "managed_dm_device", arg: "/dev/mapper/fc-rootfs-test", ok: true},
		{name: "outside_state", arg: "/etc/shadow", ok: false},
		{name: "wrong_dm", arg: "/dev/mapper/not-banger", ok: false},
		{name: "relative", arg: "rootfs.ext4", ok: false},
		{name: "empty", arg: "", ok: false},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			err := srv.validateExt4ImagePath(tc.arg)
			if tc.ok && err != nil {
				t.Fatalf("validateExt4ImagePath(%q) = %v, want nil", tc.arg, err)
			}
			if !tc.ok && err == nil {
				t.Fatalf("validateExt4ImagePath(%q) succeeded, want error", tc.arg)
			}
		})
	}
}

func TestValidateNotSymlink(t *testing.T) {
	t.Parallel()

	dir := t.TempDir()
	regular := filepath.Join(dir, "real")
	if err := os.WriteFile(regular, []byte("ok"), 0o600); err != nil {
		t.Fatalf("write regular: %v", err)
	}
	link := filepath.Join(dir, "link")
	if err := os.Symlink(regular, link); err != nil {
		t.Fatalf("symlink: %v", err)
	}

	if err := validateNotSymlink(regular); err != nil {
		t.Fatalf("validateNotSymlink(real) = %v, want nil", err)
	}
	if err := validateNotSymlink(link); err == nil {
		t.Fatal("validateNotSymlink(symlink) succeeded, want error")
	}
	if err := validateNotSymlink(filepath.Join(dir, "missing")); err == nil {
		t.Fatal("validateNotSymlink(missing) succeeded, want error")
	}
	// Symlink pointing into the system tree is the threat we care about.
	// A daemon-uid attacker plants this kind of link and hopes the helper
	// follows it; this test pins the rejection.
	hostileLink := filepath.Join(dir, "hostile")
	if err := os.Symlink("/etc/shadow", hostileLink); err != nil {
		t.Fatalf("symlink: %v", err)
	}
	if err := validateNotSymlink(hostileLink); err == nil {
		t.Fatal("validateNotSymlink(symlink-to-/etc/shadow) succeeded, want error")
	}
}

func TestValidateLaunchDrivePathAllowsManagedRootDMDevice(t *testing.T) {
	t.Parallel()

	srv := &Server{}
	if err := srv.validateLaunchDrivePath(firecracker.DriveConfig{
		ID:     "rootfs",
		Path:   "/dev/mapper/fc-rootfs-test",
		IsRoot: true,
	}, "/var/lib/banger"); err != nil {
		t.Fatalf("validateLaunchDrivePath(root dm) = %v, want nil", err)
	}

	if err := srv.validateLaunchDrivePath(firecracker.DriveConfig{
		ID:     "work",
		Path:   "/dev/mapper/fc-rootfs-test",
		IsRoot: false,
	}, "/var/lib/banger"); err == nil {
		t.Fatal("validateLaunchDrivePath(non-root dm) succeeded, want error")
	}
}
