mirror of
https://github.com/containers/podman.git
synced 2026-07-10 07:15:05 -04:00
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>
122 lines
4.2 KiB
Go
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
|
|
}
|