Resurrect auto-port reassignment, but for all providers

- Updates common to pull in new locked edit

[NO NEW TESTS NEEDED]

Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
This commit is contained in:
Jason T. Greene
2024-03-03 17:20:52 -06:00
parent ef7727238a
commit 6272abbbb8
20 changed files with 312 additions and 303 deletions

2
go.mod
View File

@ -11,7 +11,7 @@ require (
github.com/checkpoint-restore/go-criu/v7 v7.0.0 github.com/checkpoint-restore/go-criu/v7 v7.0.0
github.com/containernetworking/plugins v1.4.0 github.com/containernetworking/plugins v1.4.0
github.com/containers/buildah v1.34.1-0.20240229193131-f5d7689ef4cd github.com/containers/buildah v1.34.1-0.20240229193131-f5d7689ef4cd
github.com/containers/common v0.57.1-0.20240229165734-cec09922602e github.com/containers/common v0.57.1-0.20240304165751-a0d555c70d52
github.com/containers/conmon v2.0.20+incompatible github.com/containers/conmon v2.0.20+incompatible
github.com/containers/gvisor-tap-vsock v0.7.3 github.com/containers/gvisor-tap-vsock v0.7.3
github.com/containers/image/v5 v5.29.3-0.20240229213915-cdc68020a24f github.com/containers/image/v5 v5.29.3-0.20240229213915-cdc68020a24f

4
go.sum
View File

@ -76,8 +76,8 @@ github.com/containernetworking/plugins v1.4.0 h1:+w22VPYgk7nQHw7KT92lsRmuToHvb7w
github.com/containernetworking/plugins v1.4.0/go.mod h1:UYhcOyjefnrQvKvmmyEKsUA+M9Nfn7tqULPpH0Pkcj0= github.com/containernetworking/plugins v1.4.0/go.mod h1:UYhcOyjefnrQvKvmmyEKsUA+M9Nfn7tqULPpH0Pkcj0=
github.com/containers/buildah v1.34.1-0.20240229193131-f5d7689ef4cd h1:4cHNzaywyyJsCAtwUKMZm8r/wqm/WuNC70GfnI3kh18= github.com/containers/buildah v1.34.1-0.20240229193131-f5d7689ef4cd h1:4cHNzaywyyJsCAtwUKMZm8r/wqm/WuNC70GfnI3kh18=
github.com/containers/buildah v1.34.1-0.20240229193131-f5d7689ef4cd/go.mod h1:3fn5edBIPpIOPJYdnxBdTF7bjnBHhqZwYK5a6ApNdyk= github.com/containers/buildah v1.34.1-0.20240229193131-f5d7689ef4cd/go.mod h1:3fn5edBIPpIOPJYdnxBdTF7bjnBHhqZwYK5a6ApNdyk=
github.com/containers/common v0.57.1-0.20240229165734-cec09922602e h1:TPgCd6bWFyliJxCXEiCI1LnbB3kBUkpx1dw51ngDjWI= github.com/containers/common v0.57.1-0.20240304165751-a0d555c70d52 h1:+rq1qOOEv/2Sa1A9Tmv7yKuOzea8W2n6kFUH+bon61Y=
github.com/containers/common v0.57.1-0.20240229165734-cec09922602e/go.mod h1:8irlyBcVooYx0F+YmoY7PQPAIgdJvCj17bvL7PqeaxI= github.com/containers/common v0.57.1-0.20240304165751-a0d555c70d52/go.mod h1:h92alKzSekxVC+VDaX4gt7RJpXvJKF79a9TnULZ5ZDc=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/gvisor-tap-vsock v0.7.3 h1:yORnf15sP+sLFhxLNLgmB5/lOhldn9dRMHx/tmYtSOQ= github.com/containers/gvisor-tap-vsock v0.7.3 h1:yORnf15sP+sLFhxLNLgmB5/lOhldn9dRMHx/tmYtSOQ=

View File

@ -313,6 +313,11 @@ func (a AppleHVStubber) StopHostNetworking(_ *vmconfigs.MachineConfig, _ define.
return nil return nil
} }
func (a AppleHVStubber) UpdateSSHPort(mc *vmconfigs.MachineConfig, port int) error {
// managed by gvproxy on this backend, so nothing to do
return nil
}
func (a AppleHVStubber) VMType() define.VMType { func (a AppleHVStubber) VMType() define.VMType {
return define.AppleHvVirt return define.AppleHvVirt
} }

View File

@ -1,3 +1,5 @@
//go:build amd64 || arm64
package connection package connection
import ( import (
@ -14,19 +16,8 @@ func AddSSHConnectionsToPodmanSocket(uid, port int, identityPath, name, remoteUs
fmt.Println("An ignition path was provided. No SSH connection was added to Podman") fmt.Println("An ignition path was provided. No SSH connection was added to Podman")
return nil return nil
} }
uri := makeSSHURL(LocalhostIP, fmt.Sprintf("/run/user/%d/podman/podman.sock", uid), strconv.Itoa(port), remoteUsername)
uriRoot := makeSSHURL(LocalhostIP, "/run/podman/podman.sock", strconv.Itoa(port), "root")
cons := []connection{ cons := createConnections(name, uid, port, remoteUsername)
{
name: name,
uri: uri,
},
{
name: name + "-root",
uri: uriRoot,
},
}
// The first connection defined when connections is empty will become the default // The first connection defined when connections is empty will become the default
// regardless of IsDefault, so order according to rootful // regardless of IsDefault, so order according to rootful
@ -36,3 +27,19 @@ func AddSSHConnectionsToPodmanSocket(uid, port int, identityPath, name, remoteUs
return addConnection(cons, identityPath, opts.IsDefault) return addConnection(cons, identityPath, opts.IsDefault)
} }
func createConnections(name string, uid, port int, remoteUsername string) []connection {
uri := makeSSHURL(LocalhostIP, fmt.Sprintf("/run/user/%d/podman/podman.sock", uid), strconv.Itoa(port), remoteUsername)
uriRoot := makeSSHURL(LocalhostIP, "/run/podman/podman.sock", strconv.Itoa(port), "root")
return []connection{
{
name: name,
uri: uri,
},
{
name: name + "-root",
uri: uriRoot,
},
}
}

View File

@ -55,14 +55,17 @@ func addConnection(cons []connection, identity string, isDefault bool) error {
}) })
} }
func ChangeConnectionURI(name string, uri fmt.Stringer) error { func UpdateConnectionPairPort(name string, port, uid int, remoteUsername string, identityPath string) error {
cons := createConnections(name, uid, port, remoteUsername)
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error { return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
dst, ok := cfg.Connection.Connections[name] for _, con := range cons {
if !ok { dst := config.Destination{
return errors.New("connection not found") IsMachine: true,
URI: con.uri.String(),
Identity: identityPath,
}
cfg.Connection.Connections[name] = dst
} }
dst.URI = uri.String()
cfg.Connection.Connections[name] = dst
return nil return nil
}) })

View File

@ -446,6 +446,11 @@ func (h HyperVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo b
return err return err
} }
func (h HyperVStubber) UpdateSSHPort(mc *vmconfigs.MachineConfig, port int) error {
// managed by gvproxy on this backend, so nothing to do
return nil
}
func (h HyperVStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error { func (h HyperVStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error {
return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, h.VMType(), mc.Name) return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, h.VMType(), mc.Name)
} }

View File

@ -1,3 +1,5 @@
//go:build amd64 || arm64
package ignition package ignition
import ( import (

View File

@ -1,4 +1,4 @@
//go:build !darwin //go:build linux || freebsd
package qemu package qemu

View File

@ -1,4 +1,4 @@
//go:build windows && amd64 //go:build tempoff
package qemu package qemu

View File

@ -1,4 +1,4 @@
//go:build !darwin //go:build linux || freebsd
package qemu package qemu
@ -352,6 +352,11 @@ func (q *QEMUStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo bo
return nil return nil
} }
func (q *QEMUStubber) UpdateSSHPort(mc *vmconfigs.MachineConfig, port int) error {
// managed by gvproxy on this backend, so nothing to do
return nil
}
func (q *QEMUStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error { func (q *QEMUStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error {
return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, q.VMType(), mc.Name) return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, q.VMType(), mc.Name)
} }

View File

@ -11,8 +11,10 @@ import (
"github.com/containers/common/pkg/config" "github.com/containers/common/pkg/config"
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/containers/podman/v5/pkg/machine" "github.com/containers/podman/v5/pkg/machine"
"github.com/containers/podman/v5/pkg/machine/connection"
"github.com/containers/podman/v5/pkg/machine/define" "github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/pkg/machine/env" "github.com/containers/podman/v5/pkg/machine/env"
"github.com/containers/podman/v5/pkg/machine/ports"
"github.com/containers/podman/v5/pkg/machine/vmconfigs" "github.com/containers/podman/v5/pkg/machine/vmconfigs"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -91,6 +93,14 @@ func startHostForwarder(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvid
} }
func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider) (string, machine.APIForwardingState, error) { func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider) (string, machine.APIForwardingState, error) {
// Check if SSH port is in use, and reassign if necessary
if !ports.IsLocalPortAvailable(mc.SSH.Port) {
logrus.Warnf("detected port conflict on machine ssh port [%d], reassigning", mc.SSH.Port)
if err := reassignSSHPort(mc, provider); err != nil {
return "", 0, err
}
}
// Provider has its own networking code path (e.g. WSL) // Provider has its own networking code path (e.g. WSL)
if provider.UseProviderNetworkSetup() { if provider.UseProviderNetworkSetup() {
return "", 0, provider.StartNetworking(mc, nil) return "", 0, provider.StartNetworking(mc, nil)
@ -153,6 +163,53 @@ func conductVMReadinessCheck(mc *vmconfigs.MachineConfig, maxBackoffs int, backo
return return
} }
func reassignSSHPort(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider) error {
newPort, err := ports.AllocateMachinePort()
if err != nil {
return err
}
success := false
defer func() {
if !success {
if err := ports.ReleaseMachinePort(newPort); err != nil {
logrus.Warnf("could not release port allocation as part of failure rollback (%d): %s", newPort, err.Error())
}
}
}()
// Write a transient invalid port, to force a retry on failure
oldPort := mc.SSH.Port
mc.SSH.Port = 0
if err := mc.Write(); err != nil {
return err
}
if err := ports.ReleaseMachinePort(oldPort); err != nil {
logrus.Warnf("could not release current ssh port allocation (%d): %s", oldPort, err.Error())
}
// Update the backend's settings if relevant (e.g. WSL)
if err := provider.UpdateSSHPort(mc, newPort); err != nil {
return err
}
mc.SSH.Port = newPort
if err := connection.UpdateConnectionPairPort(mc.Name, newPort, mc.HostUser.UID, mc.SSH.RemoteUsername, mc.SSH.IdentityPath); err != nil {
return fmt.Errorf("could not update remote connection configuration: %w", err)
}
// Write updated port back
if err := mc.Write(); err != nil {
return err
}
// inform defer routine not to release the port
success = true
return nil
}
func isListening(port int) bool { func isListening(port int) bool {
// Check if we can dial it // Check if we can dial it
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", port), 10*time.Millisecond) conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", port), 10*time.Millisecond)

View File

@ -96,6 +96,7 @@ type VMProvider interface { //nolint:interfacebloat
UserModeNetworkEnabled(mc *MachineConfig) bool UserModeNetworkEnabled(mc *MachineConfig) bool
UseProviderNetworkSetup() bool UseProviderNetworkSetup() bool
RequireExclusiveActive() bool RequireExclusiveActive() bool
UpdateSSHPort(mc *MachineConfig, port int) error
} }
// HostUser describes the host user // HostUser describes the host user

View File

@ -15,7 +15,7 @@ import (
"github.com/containers/podman/v5/pkg/machine/connection" "github.com/containers/podman/v5/pkg/machine/connection"
"github.com/containers/podman/v5/pkg/machine/define" "github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/pkg/machine/lock" "github.com/containers/podman/v5/pkg/machine/lock"
"github.com/containers/podman/v5/utils" "github.com/containers/podman/v5/pkg/machine/ports"
"github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/ioutils"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -78,8 +78,7 @@ func NewMachineConfig(opts define.InitOptions, dirs *define.MachineDirs, sshIden
} }
mc.Resources = mrc mc.Resources = mrc
// TODO WSL had a locking port mechanism, we should consider this. sshPort, err := ports.AllocateMachinePort()
sshPort, err := utils.GetRandomPort()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -204,6 +203,11 @@ func (mc *MachineConfig) Remove(saveIgnition, saveImage bool) ([]string, func()
if err := mc.configPath.Delete(); err != nil { if err := mc.configPath.Delete(); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
if err := ports.ReleaseMachinePort(mc.SSH.Port); err != nil {
errs = append(errs, err)
}
return errorhandling.JoinErrors(errs) return errorhandling.JoinErrors(errs)
} }

View File

@ -11,7 +11,6 @@ import (
"strings" "strings"
"github.com/containers/podman/v5/pkg/machine/ocipull" "github.com/containers/podman/v5/pkg/machine/ocipull"
"github.com/containers/podman/v5/pkg/machine/ports"
"github.com/containers/podman/v5/pkg/machine/shim/diskpull" "github.com/containers/podman/v5/pkg/machine/shim/diskpull"
"github.com/containers/podman/v5/pkg/machine/stdpull" "github.com/containers/podman/v5/pkg/machine/stdpull"
"github.com/containers/podman/v5/pkg/machine/wsl/wutil" "github.com/containers/podman/v5/pkg/machine/wsl/wutil"
@ -111,7 +110,7 @@ func (w WSLStubber) Remove(mc *vmconfigs.MachineConfig) ([]string, func() error,
if err := runCmdPassThrough(wutil.FindWSL(), "--unregister", machine.ToDist(mc.Name)); err != nil { if err := runCmdPassThrough(wutil.FindWSL(), "--unregister", machine.ToDist(mc.Name)); err != nil {
logrus.Error(err) logrus.Error(err)
} }
return ports.ReleaseMachinePort(mc.SSH.Port) return nil
} }
return []string{}, wslRemoveFunc, nil return []string{}, wslRemoveFunc, nil
@ -205,15 +204,6 @@ func (w WSLStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo bool
func (w WSLStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func() error, error) { func (w WSLStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func() error, error) {
dist := machine.ToDist(mc.Name) dist := machine.ToDist(mc.Name)
// TODO The original code checked to see if the SSH port was actually open and re-assigned if it was
// we could consider this but it should be higher up the stack
// if !machine.IsLocalPortAvailable(v.Port) {
// logrus.Warnf("SSH port conflict detected, reassigning a new port")
// if err := v.reassignSshPort(); err != nil {
// return err
// }
// }
err := wslInvoke(dist, "/root/bootstrap") err := wslInvoke(dist, "/root/bootstrap")
if err != nil { if err != nil {
err = fmt.Errorf("the WSL bootstrap script failed: %w", err) err = fmt.Errorf("the WSL bootstrap script failed: %w", err)
@ -279,6 +269,16 @@ func (w WSLStubber) StopHostNetworking(mc *vmconfigs.MachineConfig, vmType defin
return stopUserModeNetworking(mc) return stopUserModeNetworking(mc)
} }
func (w WSLStubber) UpdateSSHPort(mc *vmconfigs.MachineConfig, port int) error {
dist := machine.ToDist(mc.Name)
if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(changePort, port)); err != nil {
return fmt.Errorf("could not change SSH port for guest OS: %w", err)
}
return nil
}
func (w WSLStubber) VMType() define.VMType { func (w WSLStubber) VMType() define.VMType {
return define.WSLVirt return define.WSLVirt
} }

View File

@ -13,6 +13,9 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync"
"syscall"
"time"
"github.com/containers/storage/pkg/unshare" "github.com/containers/storage/pkg/unshare"
systemdDbus "github.com/coreos/go-systemd/v22/dbus" systemdDbus "github.com/coreos/go-systemd/v22/dbus"
@ -22,6 +25,7 @@ import (
"github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"golang.org/x/sys/unix"
) )
var ( var (
@ -30,6 +34,10 @@ var (
// ErrCgroupV1Rootless means the cgroup v1 were attempted to be used in rootless environment // ErrCgroupV1Rootless means the cgroup v1 were attempted to be used in rootless environment
ErrCgroupV1Rootless = errors.New("no support for CGroups V1 in rootless environments") ErrCgroupV1Rootless = errors.New("no support for CGroups V1 in rootless environments")
ErrStatCgroup = errors.New("no cgroup available for gathering user statistics") ErrStatCgroup = errors.New("no cgroup available for gathering user statistics")
isUnifiedOnce sync.Once
isUnified bool
isUnifiedErr error
) )
// CgroupControl controls a cgroup hierarchy // CgroupControl controls a cgroup hierarchy
@ -731,3 +739,139 @@ func SystemCPUUsage() (uint64, error) {
} }
return total, nil return total, nil
} }
// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode.
func IsCgroup2UnifiedMode() (bool, error) {
isUnifiedOnce.Do(func() {
var st syscall.Statfs_t
if err := syscall.Statfs("/sys/fs/cgroup", &st); err != nil {
isUnified, isUnifiedErr = false, err
} else {
isUnified, isUnifiedErr = st.Type == unix.CGROUP2_SUPER_MAGIC, nil
}
})
return isUnified, isUnifiedErr
}
// UserConnection returns an user connection to D-BUS
func UserConnection(uid int) (*systemdDbus.Conn, error) {
return systemdDbus.NewConnection(func() (*dbus.Conn, error) {
return dbusAuthConnection(uid, dbus.SessionBusPrivateNoAutoStartup)
})
}
// UserOwnsCurrentSystemdCgroup checks whether the current EUID owns the
// current cgroup.
func UserOwnsCurrentSystemdCgroup() (bool, error) {
uid := os.Geteuid()
cgroup2, err := IsCgroup2UnifiedMode()
if err != nil {
return false, err
}
f, err := os.Open("/proc/self/cgroup")
if err != nil {
return false, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, ":", 3)
if len(parts) < 3 {
continue
}
var cgroupPath string
if cgroup2 {
cgroupPath = filepath.Join(cgroupRoot, parts[2])
} else {
if parts[1] != "name=systemd" {
continue
}
cgroupPath = filepath.Join(cgroupRoot, "systemd", parts[2])
}
st, err := os.Stat(cgroupPath)
if err != nil {
return false, err
}
s := st.Sys()
if s == nil {
return false, fmt.Errorf("stat cgroup path %s", cgroupPath)
}
if int(s.(*syscall.Stat_t).Uid) != uid {
return false, nil
}
}
if err := scanner.Err(); err != nil {
return false, fmt.Errorf("parsing file /proc/self/cgroup: %w", err)
}
return true, nil
}
// rmDirRecursively delete recursively a cgroup directory.
// It differs from os.RemoveAll as it doesn't attempt to unlink files.
// On cgroupfs we are allowed only to rmdir empty directories.
func rmDirRecursively(path string) error {
killProcesses := func(signal syscall.Signal) {
if signal == unix.SIGKILL {
if err := os.WriteFile(filepath.Join(path, "cgroup.kill"), []byte("1"), 0o600); err == nil {
return
}
}
// kill all the processes that are still part of the cgroup
if procs, err := os.ReadFile(filepath.Join(path, "cgroup.procs")); err == nil {
for _, pidS := range strings.Split(string(procs), "\n") {
if pid, err := strconv.Atoi(pidS); err == nil {
_ = unix.Kill(pid, signal)
}
}
}
}
if err := os.Remove(path); err == nil || errors.Is(err, os.ErrNotExist) {
return nil
}
entries, err := os.ReadDir(path)
if err != nil {
return err
}
for _, i := range entries {
if i.IsDir() {
if err := rmDirRecursively(filepath.Join(path, i.Name())); err != nil {
return err
}
}
}
attempts := 0
for {
err := os.Remove(path)
if err == nil || errors.Is(err, os.ErrNotExist) {
return nil
}
if errors.Is(err, unix.EBUSY) {
// send a SIGTERM after 3 second
if attempts == 300 {
killProcesses(unix.SIGTERM)
}
// send SIGKILL after 8 seconds
if attempts == 800 {
killProcesses(unix.SIGKILL)
}
// give up after 10 seconds
if attempts < 1000 {
time.Sleep(time.Millisecond * 10)
attempts++
continue
}
}
return fmt.Errorf("remove %s: %w", path, err)
}
}

View File

@ -1,162 +0,0 @@
//go:build linux
package cgroups
import (
"bufio"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
"github.com/godbus/dbus/v5"
"golang.org/x/sys/unix"
)
var (
isUnifiedOnce sync.Once
isUnified bool
isUnifiedErr error
)
// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode.
func IsCgroup2UnifiedMode() (bool, error) {
isUnifiedOnce.Do(func() {
var st syscall.Statfs_t
if err := syscall.Statfs("/sys/fs/cgroup", &st); err != nil {
isUnified, isUnifiedErr = false, err
} else {
isUnified, isUnifiedErr = st.Type == unix.CGROUP2_SUPER_MAGIC, nil
}
})
return isUnified, isUnifiedErr
}
// UserConnection returns an user connection to D-BUS
func UserConnection(uid int) (*systemdDbus.Conn, error) {
return systemdDbus.NewConnection(func() (*dbus.Conn, error) {
return dbusAuthConnection(uid, dbus.SessionBusPrivateNoAutoStartup)
})
}
// UserOwnsCurrentSystemdCgroup checks whether the current EUID owns the
// current cgroup.
func UserOwnsCurrentSystemdCgroup() (bool, error) {
uid := os.Geteuid()
cgroup2, err := IsCgroup2UnifiedMode()
if err != nil {
return false, err
}
f, err := os.Open("/proc/self/cgroup")
if err != nil {
return false, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, ":", 3)
if len(parts) < 3 {
continue
}
var cgroupPath string
if cgroup2 {
cgroupPath = filepath.Join(cgroupRoot, parts[2])
} else {
if parts[1] != "name=systemd" {
continue
}
cgroupPath = filepath.Join(cgroupRoot, "systemd", parts[2])
}
st, err := os.Stat(cgroupPath)
if err != nil {
return false, err
}
s := st.Sys()
if s == nil {
return false, fmt.Errorf("stat cgroup path %s", cgroupPath)
}
if int(s.(*syscall.Stat_t).Uid) != uid {
return false, nil
}
}
if err := scanner.Err(); err != nil {
return false, fmt.Errorf("parsing file /proc/self/cgroup: %w", err)
}
return true, nil
}
// rmDirRecursively delete recursively a cgroup directory.
// It differs from os.RemoveAll as it doesn't attempt to unlink files.
// On cgroupfs we are allowed only to rmdir empty directories.
func rmDirRecursively(path string) error {
killProcesses := func(signal syscall.Signal) {
if signal == unix.SIGKILL {
if err := os.WriteFile(filepath.Join(path, "cgroup.kill"), []byte("1"), 0o600); err == nil {
return
}
}
// kill all the processes that are still part of the cgroup
if procs, err := os.ReadFile(filepath.Join(path, "cgroup.procs")); err == nil {
for _, pidS := range strings.Split(string(procs), "\n") {
if pid, err := strconv.Atoi(pidS); err == nil {
_ = unix.Kill(pid, signal)
}
}
}
}
if err := os.Remove(path); err == nil || errors.Is(err, os.ErrNotExist) {
return nil
}
entries, err := os.ReadDir(path)
if err != nil {
return err
}
for _, i := range entries {
if i.IsDir() {
if err := rmDirRecursively(filepath.Join(path, i.Name())); err != nil {
return err
}
}
}
attempts := 0
for {
err := os.Remove(path)
if err == nil || errors.Is(err, os.ErrNotExist) {
return nil
}
if errors.Is(err, unix.EBUSY) {
// send a SIGTERM after 3 second
if attempts == 300 {
killProcesses(unix.SIGTERM)
}
// send SIGKILL after 8 seconds
if attempts == 800 {
killProcesses(unix.SIGKILL)
}
// give up after 10 seconds
if attempts < 1000 {
time.Sleep(time.Millisecond * 10)
attempts++
continue
}
}
return fmt.Errorf("remove %s: %w", path, err)
}
}

View File

@ -3,10 +3,7 @@
package cgroups package cgroups
import ( import (
"fmt"
"os" "os"
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
) )
// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode. // IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode.
@ -23,8 +20,3 @@ func UserOwnsCurrentSystemdCgroup() (bool, error) {
func rmDirRecursively(path string) error { func rmDirRecursively(path string) error {
return os.RemoveAll(path) return os.RemoveAll(path)
} }
// UserConnection returns an user connection to D-BUS
func UserConnection(uid int) (*systemdDbus.Conn, error) {
return nil, fmt.Errorf("systemd d-bus is not supported on this platform")
}

View File

@ -1,80 +0,0 @@
//go:build !linux
package cgroups
import (
"context"
"fmt"
"path/filepath"
"strings"
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
"github.com/godbus/dbus/v5"
)
func systemdCreate(path string, c *systemdDbus.Conn) error {
slice, name := filepath.Split(path)
slice = strings.TrimSuffix(slice, "/")
var lastError error
for i := 0; i < 2; i++ {
properties := []systemdDbus.Property{
systemdDbus.PropDescription(fmt.Sprintf("cgroup %s", name)),
systemdDbus.PropWants(slice),
}
pMap := map[string]bool{
"DefaultDependencies": false,
"MemoryAccounting": true,
"CPUAccounting": true,
"BlockIOAccounting": true,
}
if i == 0 {
pMap["Delegate"] = true
}
for k, v := range pMap {
p := systemdDbus.Property{
Name: k,
Value: dbus.MakeVariant(v),
}
properties = append(properties, p)
}
ch := make(chan string)
_, err := c.StartTransientUnitContext(context.TODO(), name, "replace", properties, ch)
if err != nil {
lastError = err
continue
}
<-ch
return nil
}
return lastError
}
/*
systemdDestroyConn is copied from containerd/cgroups/systemd.go file, that
has the following license:
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
func systemdDestroyConn(path string, c *systemdDbus.Conn) error {
name := filepath.Base(path)
ch := make(chan string)
_, err := c.StopUnitContext(context.TODO(), name, "replace", ch)
if err != nil {
return err
}
<-ch
return nil
}

View File

@ -9,6 +9,7 @@ import (
"path/filepath" "path/filepath"
"github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/lockfile"
) )
const connectionsFile = "podman-connections.json" const connectionsFile = "podman-connections.json"
@ -64,28 +65,24 @@ type Farm struct {
ReadWrite bool ReadWrite bool
} }
func readConnectionConf() (*ConnectionsFile, string, error) { func readConnectionConf(path string) (*ConnectionsFile, error) {
path, err := connectionsConfigFile()
if err != nil {
return nil, "", err
}
conf := new(ConnectionsFile) conf := new(ConnectionsFile)
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
// return empty config if file does not exists // return empty config if file does not exists
if errors.Is(err, fs.ErrNotExist) { if errors.Is(err, fs.ErrNotExist) {
return conf, path, nil return conf, nil
} }
return nil, "", err return nil, err
} }
defer f.Close() defer f.Close()
err = json.NewDecoder(f).Decode(conf) err = json.NewDecoder(f).Decode(conf)
if err != nil { if err != nil {
return nil, "", fmt.Errorf("parse %q: %w", path, err) return nil, fmt.Errorf("parse %q: %w", path, err)
} }
return conf, path, nil return conf, nil
} }
func writeConnectionConf(path string, conf *ConnectionsFile) error { func writeConnectionConf(path string, conf *ConnectionsFile) error {
@ -113,7 +110,20 @@ func writeConnectionConf(path string, conf *ConnectionsFile) error {
// The function will read and write the file automatically and the // The function will read and write the file automatically and the
// callback function just needs to modify the cfg as needed. // callback function just needs to modify the cfg as needed.
func EditConnectionConfig(callback func(cfg *ConnectionsFile) error) error { func EditConnectionConfig(callback func(cfg *ConnectionsFile) error) error {
conf, path, err := readConnectionConf() path, err := connectionsConfigFile()
if err != nil {
return err
}
lockPath := path + ".lock"
lock, err := lockfile.GetLockFile(lockPath)
if err != nil {
return fmt.Errorf("obtain lock file: %w", err)
}
lock.Lock()
defer lock.Unlock()
conf, err := readConnectionConf(path)
if err != nil { if err != nil {
return fmt.Errorf("read connections file: %w", err) return fmt.Errorf("read connections file: %w", err)
} }
@ -139,7 +149,11 @@ func makeConnection(name string, dst Destination, def, readWrite bool) *Connecti
// GetConnection return the connection for the given name or if def is set to true then return the default connection. // GetConnection return the connection for the given name or if def is set to true then return the default connection.
func (c *Config) GetConnection(name string, def bool) (*Connection, error) { func (c *Config) GetConnection(name string, def bool) (*Connection, error) {
conConf, _, err := readConnectionConf() path, err := connectionsConfigFile()
if err != nil {
return nil, err
}
conConf, err := readConnectionConf(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -167,7 +181,11 @@ func (c *Config) GetConnection(name string, def bool) (*Connection, error) {
// GetAllConnections return all configured connections // GetAllConnections return all configured connections
func (c *Config) GetAllConnections() ([]Connection, error) { func (c *Config) GetAllConnections() ([]Connection, error) {
conConf, _, err := readConnectionConf() path, err := connectionsConfigFile()
if err != nil {
return nil, err
}
conConf, err := readConnectionConf(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -222,7 +240,11 @@ func (c *Config) GetDefaultFarmConnections() (string, []Connection, error) {
// if def is true it will use the default farm instead of the name. // if def is true it will use the default farm instead of the name.
// Returns the name of the farm and the connections for it. // Returns the name of the farm and the connections for it.
func (c *Config) getFarmConnections(name string, def bool) (string, []Connection, error) { func (c *Config) getFarmConnections(name string, def bool) (string, []Connection, error) {
conConf, _, err := readConnectionConf() path, err := connectionsConfigFile()
if err != nil {
return "", nil, err
}
conConf, err := readConnectionConf(path)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@ -259,7 +281,11 @@ func makeFarm(name string, cons []string, def, readWrite bool) Farm {
// GetAllFarms returns all configured farms // GetAllFarms returns all configured farms
func (c *Config) GetAllFarms() ([]Farm, error) { func (c *Config) GetAllFarms() ([]Farm, error) {
conConf, _, err := readConnectionConf() path, err := connectionsConfigFile()
if err != nil {
return nil, err
}
conConf, err := readConnectionConf(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }

2
vendor/modules.txt vendored
View File

@ -171,7 +171,7 @@ github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util github.com/containers/buildah/pkg/util
github.com/containers/buildah/pkg/volumes github.com/containers/buildah/pkg/volumes
github.com/containers/buildah/util github.com/containers/buildah/util
# github.com/containers/common v0.57.1-0.20240229165734-cec09922602e # github.com/containers/common v0.57.1-0.20240304165751-a0d555c70d52
## explicit; go 1.20 ## explicit; go 1.20
github.com/containers/common/internal github.com/containers/common/internal
github.com/containers/common/internal/attributedstring github.com/containers/common/internal/attributedstring