Merge pull request #28915 from chanhyeokseo/fix-machine-virtiofs-fcos-home-28911

machine: fix virtiofs mount targets on FCOS
This commit is contained in:
Ashley Cui
2026-06-17 10:36:51 -04:00
committed by GitHub
2 changed files with 109 additions and 2 deletions

View File

@@ -7,6 +7,38 @@ import (
"go.podman.io/podman/v6/pkg/systemd/parser"
)
// fcosDirSymlinks maps FCOS root-level symlinks to their real paths.
// FCOS symlinks several top-level dirs into /var for the read-only rootfs.
// systemd rejects a mount unit whose Where= traverses a symlink, so we
// resolve these before writing the ignition unit.
var fcosDirSymlinks = map[string]string{
"/home": "/var/home",
"/mnt": "/var/mnt",
"/opt": "/var/opt",
"/root": "/var/roothome",
"/srv": "/var/srv",
}
// canonicalizeFCOSMountTarget returns the canonical path for target by
// substituting any known FCOS root-level symlink prefix.
func canonicalizeFCOSMountTarget(target string) string {
// Guard check for empty strings or paths not starting with '/'
if len(target) < 2 || target[0] != '/' {
return target
}
// Find the end of the first path component (e.g. "/home" in "/home/alice").
end := 1
for end < len(target) && target[end] != '/' {
end++
}
if real, ok := fcosDirSymlinks[target[:end]]; ok {
return real + target[end:]
}
return target
}
// GenerateSystemDFilesForVirtiofsMounts generates the systemd unit files needed
// to mount virtiofs volumes inside a FCOS guest VM. It is shared between the
// AppleHV, LibKrun, and QEMU providers.
@@ -30,10 +62,13 @@ func GenerateSystemDFilesForVirtiofsMounts(mounts []VirtIoFs) ([]ignition.Unit,
return nil, err
}
// Use the canonical path so systemd accepts the Where= value.
// On FCOS /home is a symlink to var/home; systemd rejects non-canonical paths.
canonicalTarget := canonicalizeFCOSMountTarget(mnt.Target)
virtiofsMount := ignition.Unit{
Enabled: ignition.BoolToPtr(true),
Name: fmt.Sprintf("%s.mount", parser.PathEscape(mnt.Target)),
Contents: ignition.StrToPtr(fmt.Sprintf(mountUnitFile, mnt.Tag, mnt.Target)),
Name: fmt.Sprintf("%s.mount", parser.PathEscape(canonicalTarget)),
Contents: ignition.StrToPtr(fmt.Sprintf(mountUnitFile, mnt.Tag, canonicalTarget)),
}
unitFiles = append(unitFiles, virtiofsMount)

View File

@@ -0,0 +1,72 @@
package machine
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCanonicalizeFCOSMountTarget(t *testing.T) {
tests := []struct {
input string
expected string
}{
// Known FCOS symlinks — must be rewritten
{"/home/alice", "/var/home/alice"},
{"/home/alice/projects", "/var/home/alice/projects"},
{"/mnt/data", "/var/mnt/data"},
{"/opt/myapp", "/var/opt/myapp"},
{"/root", "/var/roothome"},
{"/root/.config", "/var/roothome/.config"},
{"/srv/www", "/var/srv/www"},
// Exact match on a symlinked dir itself
{"/home", "/var/home"},
{"/mnt", "/var/mnt"},
// Paths that do NOT start with a known symlink — unchanged
{"/var/home/alice", "/var/home/alice"},
{"/tmp/foo", "/tmp/foo"},
{"/data/work", "/data/work"},
{"/work", "/work"},
// Prefix collision guard: /homes should NOT match /home
{"/homes/alice", "/homes/alice"},
{"/rootfs", "/rootfs"},
}
for _, tt := range tests {
got := canonicalizeFCOSMountTarget(tt.input)
assert.Equal(t, tt.expected, got, "input: %q", tt.input)
}
}
func TestGenerateSystemDFilesForVirtiofsmountsCanonicalPath(t *testing.T) {
mounts := []VirtIoFs{
NewVirtIoFsMount("/home/alice", "/home/alice", false),
NewVirtIoFsMount("/data", "/data", false),
}
units, err := GenerateSystemDFilesForVirtiofsMounts(mounts)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// First two units are the mount units; the rest are immutable-root helpers.
mountUnits := units[:2]
cases := []struct {
wantName string
wantWhere string
}{
// /home/alice must be rewritten to /var/home/alice
{"var-home-alice.mount", "/var/home/alice"},
// /data has no FCOS symlink — stays as-is
{"data.mount", "/data"},
}
for i, c := range cases {
u := mountUnits[i]
assert.Equal(t, c.wantName, u.Name, "unit[%d].Name", i)
if assert.NotNil(t, u.Contents, "unit[%d].Contents", i) {
assert.Contains(t, *u.Contents, "Where="+c.wantWhere, "unit[%d] missing Where=", i)
}
}
}