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

View File

@ -13,6 +13,9 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/containers/storage/pkg/unshare"
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
@ -22,6 +25,7 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/sirupsen/logrus"
"golang.org/x/exp/maps"
"golang.org/x/sys/unix"
)
var (
@ -30,6 +34,10 @@ var (
// ErrCgroupV1Rootless means the cgroup v1 were attempted to be used in rootless environment
ErrCgroupV1Rootless = errors.New("no support for CGroups V1 in rootless environments")
ErrStatCgroup = errors.New("no cgroup available for gathering user statistics")
isUnifiedOnce sync.Once
isUnified bool
isUnifiedErr error
)
// CgroupControl controls a cgroup hierarchy
@ -731,3 +739,139 @@ func SystemCPUUsage() (uint64, error) {
}
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
import (
"fmt"
"os"
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
)
// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode.
@ -23,8 +20,3 @@ func UserOwnsCurrentSystemdCgroup() (bool, error) {
func rmDirRecursively(path string) error {
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"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/lockfile"
)
const connectionsFile = "podman-connections.json"
@ -64,28 +65,24 @@ type Farm struct {
ReadWrite bool
}
func readConnectionConf() (*ConnectionsFile, string, error) {
path, err := connectionsConfigFile()
if err != nil {
return nil, "", err
}
func readConnectionConf(path string) (*ConnectionsFile, error) {
conf := new(ConnectionsFile)
f, err := os.Open(path)
if err != nil {
// return empty config if file does not exists
if errors.Is(err, fs.ErrNotExist) {
return conf, path, nil
return conf, nil
}
return nil, "", err
return nil, err
}
defer f.Close()
err = json.NewDecoder(f).Decode(conf)
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 {
@ -113,7 +110,20 @@ func writeConnectionConf(path string, conf *ConnectionsFile) error {
// The function will read and write the file automatically and the
// callback function just needs to modify the cfg as needed.
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 {
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.
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 {
return nil, err
}
@ -167,7 +181,11 @@ func (c *Config) GetConnection(name string, def bool) (*Connection, error) {
// GetAllConnections return all configured connections
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 {
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.
// Returns the name of the farm and the connections for it.
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 {
return "", nil, err
}
@ -259,7 +281,11 @@ func makeFarm(name string, cons []string, def, readWrite bool) Farm {
// GetAllFarms returns all configured farms
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 {
return nil, err
}