Files
podman/pkg/machine/volume_systemd.go
Chanhyeok Seo 61dea3749f machine: fix virtiofs mount targets on FCOS
FCOS symlinks /home to var/home. systemd rejects a mount unit whose
Where= path traverses a symlink, breaking default home dir mounts.

Resolve known FCOS symlinks before writing the ignition unit.

Fixes: #28911
Signed-off-by: Chanhyeok Seo <chanhyeok.seo@datadoghq.com>
2026-06-16 22:52:33 +09:00

122 lines
4.2 KiB
Go

package machine
import (
"fmt"
"go.podman.io/podman/v6/pkg/machine/ignition"
"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.
//
// Mounting in FCOS with virtiofs is a bit of a dance. we need a unit file for
// the mount, a unit file for automatic mounting on boot, and a "preparatory"
// service file that disables FCOS security, performs the mkdir of the mount
// point, and then re-enables security. This must be done for each mount.
func GenerateSystemDFilesForVirtiofsMounts(mounts []VirtIoFs) ([]ignition.Unit, error) {
unitFiles := make([]ignition.Unit, 0, len(mounts)+2)
for _, mnt := range mounts {
// Create mount unit for each mount
mountUnit := parser.NewUnitFile()
mountUnit.Add("Mount", "What", "%s")
mountUnit.Add("Mount", "Where", "%s")
mountUnit.Add("Mount", "Type", "virtiofs")
mountUnit.Add("Mount", "Options", fmt.Sprintf("context=\"%s\"", NFSSELinuxContext))
mountUnit.Add("Install", "WantedBy", "local-fs.target")
mountUnitFile, err := mountUnit.ToString()
if err != nil {
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(canonicalTarget)),
Contents: ignition.StrToPtr(fmt.Sprintf(mountUnitFile, mnt.Tag, canonicalTarget)),
}
unitFiles = append(unitFiles, virtiofsMount)
}
// This is a way to workaround the FCOS limitation of creating directories
// at the rootfs / and then mounting to them.
immutableRootOff := parser.NewUnitFile()
immutableRootOff.Add("Unit", "Description", "Allow systemd to create mount points on /")
immutableRootOff.Add("Unit", "DefaultDependencies", "no")
immutableRootOff.Add("Service", "Type", "oneshot")
immutableRootOff.Add("Service", "ExecStart", "chattr -i /")
immutableRootOff.Add("Install", "WantedBy", "local-fs-pre.target")
immutableRootOffFile, err := immutableRootOff.ToString()
if err != nil {
return nil, err
}
immutableRootOffUnit := ignition.Unit{
Contents: ignition.StrToPtr(immutableRootOffFile),
Name: "immutable-root-off.service",
Enabled: ignition.BoolToPtr(true),
}
unitFiles = append(unitFiles, immutableRootOffUnit)
immutableRootOn := parser.NewUnitFile()
immutableRootOn.Add("Unit", "Description", "Set / back to immutable after mounts are done")
immutableRootOn.Add("Unit", "DefaultDependencies", "no")
immutableRootOn.Add("Unit", "After", "local-fs.target")
immutableRootOn.Add("Service", "Type", "oneshot")
immutableRootOn.Add("Service", "ExecStart", "chattr +i /")
immutableRootOn.Add("Install", "WantedBy", "local-fs.target")
immutableRootOnFile, err := immutableRootOn.ToString()
if err != nil {
return nil, err
}
immutableRootOnUnit := ignition.Unit{
Contents: ignition.StrToPtr(immutableRootOnFile),
Name: "immutable-root-on.service",
Enabled: ignition.BoolToPtr(true),
}
unitFiles = append(unitFiles, immutableRootOnUnit)
return unitFiles, nil
}