152 lines
4.2 KiB
Go
152 lines
4.2 KiB
Go
package systemd
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"github.com/containers/common/pkg/cgroups"
|
|
"github.com/containers/storage/pkg/unshare"
|
|
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
|
|
"github.com/godbus/dbus/v5"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var (
|
|
runsOnSystemdOnce sync.Once
|
|
runsOnSystemd bool
|
|
)
|
|
|
|
// RunsOnSystemd returns whether the system is using systemd
|
|
func RunsOnSystemd() bool {
|
|
runsOnSystemdOnce.Do(func() {
|
|
// per sd_booted(3), check for this dir
|
|
fd, err := os.Stat("/run/systemd/system")
|
|
runsOnSystemd = err == nil && fd.IsDir()
|
|
})
|
|
return runsOnSystemd
|
|
}
|
|
|
|
func moveProcessPIDFileToScope(pidPath, slice, scope string) error {
|
|
data, err := os.ReadFile(pidPath)
|
|
if err != nil {
|
|
// do not raise an error if the file doesn't exist
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("cannot read pid file: %w", err)
|
|
}
|
|
pid, err := strconv.ParseUint(string(data), 10, 0)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot parse pid file %s: %w", pidPath, err)
|
|
}
|
|
|
|
return moveProcessToScope(int(pid), slice, scope)
|
|
}
|
|
|
|
func moveProcessToScope(pid int, slice, scope string) error {
|
|
err := RunUnderSystemdScope(pid, slice, scope)
|
|
// If the PID is not valid anymore, do not return an error.
|
|
if dbusErr, ok := err.(dbus.Error); ok {
|
|
if dbusErr.Name == "org.freedesktop.DBus.Error.UnixProcessIdUnknown" {
|
|
return nil
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// MoveRootlessNetnsSlirpProcessToUserSlice moves the slirp4netns process for the rootless netns
|
|
// into a different scope so that systemd does not kill it with a container.
|
|
func MoveRootlessNetnsSlirpProcessToUserSlice(pid int) error {
|
|
randBytes := make([]byte, 4)
|
|
_, err := rand.Read(randBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return moveProcessToScope(pid, "user.slice", fmt.Sprintf("rootless-netns-%x.scope", randBytes))
|
|
}
|
|
|
|
// MovePauseProcessToScope moves the pause process used for rootless mode to keep the namespaces alive to
|
|
// a separate scope.
|
|
func MovePauseProcessToScope(pausePidPath string) {
|
|
var err error
|
|
|
|
for i := 0; i < 10; i++ {
|
|
randBytes := make([]byte, 4)
|
|
_, err = rand.Read(randBytes)
|
|
if err != nil {
|
|
logrus.Errorf("failed to read random bytes: %v", err)
|
|
continue
|
|
}
|
|
err = moveProcessPIDFileToScope(pausePidPath, "user.slice", fmt.Sprintf("podman-pause-%x.scope", randBytes))
|
|
if err == nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
unified, err2 := cgroups.IsCgroup2UnifiedMode()
|
|
if err2 != nil {
|
|
logrus.Warnf("Failed to detect if running with cgroup unified: %v", err)
|
|
}
|
|
if RunsOnSystemd() && unified {
|
|
logrus.Warnf("Failed to add pause process to systemd sandbox cgroup: %v", err)
|
|
} else {
|
|
logrus.Debugf("Failed to add pause process to systemd sandbox cgroup: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// RunUnderSystemdScope adds the specified pid to a systemd scope
|
|
func RunUnderSystemdScope(pid int, slice string, unitName string) error {
|
|
var properties []systemdDbus.Property
|
|
var conn *systemdDbus.Conn
|
|
var err error
|
|
|
|
if unshare.GetRootlessUID() != 0 {
|
|
conn, err = cgroups.UserConnection(unshare.GetRootlessUID())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
conn, err = systemdDbus.NewWithContext(context.Background())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
defer conn.Close()
|
|
properties = append(properties, systemdDbus.PropSlice(slice))
|
|
properties = append(properties, newProp("PIDs", []uint32{uint32(pid)}))
|
|
properties = append(properties, newProp("Delegate", true))
|
|
properties = append(properties, newProp("DefaultDependencies", false))
|
|
ch := make(chan string)
|
|
_, err = conn.StartTransientUnitContext(context.Background(), unitName, "replace", properties, ch)
|
|
if err != nil {
|
|
// On errors check if the cgroup already exists, if it does move the process there
|
|
if props, err := conn.GetUnitTypePropertiesContext(context.Background(), unitName, "Scope"); err == nil {
|
|
if cgroup, ok := props["ControlGroup"].(string); ok && cgroup != "" {
|
|
if err := cgroups.MoveUnderCgroup(cgroup, "", []uint32{uint32(pid)}); err == nil {
|
|
return nil
|
|
}
|
|
// On errors return the original error message we got from StartTransientUnit.
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Block until job is started
|
|
<-ch
|
|
|
|
return nil
|
|
}
|
|
|
|
func newProp(name string, units interface{}) systemdDbus.Property {
|
|
return systemdDbus.Property{
|
|
Name: name,
|
|
Value: dbus.MakeVariant(units),
|
|
}
|
|
}
|