Files
podman/vendor/github.com/opencontainers/cgroups/fs2/freezer.go
renovate[bot] df7d787b4c Update module github.com/opencontainers/cgroups to v0.0.4
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 23:46:15 +00:00

141 lines
4.2 KiB
Go

package fs2
import (
"bufio"
"errors"
"fmt"
"os"
"strings"
"time"
"golang.org/x/sys/unix"
"github.com/opencontainers/cgroups"
)
func setFreezer(dirPath string, state cgroups.FreezerState) error {
var stateStr string
switch state {
case cgroups.Undefined:
return nil
case cgroups.Frozen:
stateStr = "1"
case cgroups.Thawed:
stateStr = "0"
default:
return fmt.Errorf("invalid freezer state %q requested", state)
}
fd, err := cgroups.OpenFile(dirPath, "cgroup.freeze", unix.O_RDWR)
if err != nil {
// We can ignore this request as long as the user didn't ask us to
// freeze the container (since without the freezer cgroup, that's a
// no-op).
if state != cgroups.Frozen {
return nil
}
return fmt.Errorf("freezer not supported: %w", err)
}
defer fd.Close()
if _, err := fd.WriteString(stateStr); err != nil {
return err
}
// Confirm that the cgroup did actually change states.
if actualState, err := readFreezer(dirPath, fd); err != nil {
return err
} else if actualState != state {
return fmt.Errorf(`expected "cgroup.freeze" to be in state %q but was in %q`, state, actualState)
}
return nil
}
func getFreezer(dirPath string) (cgroups.FreezerState, error) {
fd, err := cgroups.OpenFile(dirPath, "cgroup.freeze", unix.O_RDONLY)
if err != nil {
// If the kernel is too old, then we just treat the freezer as
// being in an "undefined" state and ignore the error.
return cgroups.Undefined, ignoreNotExistOrNoDeviceError(err)
}
defer fd.Close()
return readFreezer(dirPath, fd)
}
func readFreezer(dirPath string, fd *os.File) (cgroups.FreezerState, error) {
if _, err := fd.Seek(0, 0); err != nil {
// If the cgroup path is deleted at this point, then we just treat the freezer as
// being in an "undefined" state and ignore the error.
return cgroups.Undefined, ignoreNotExistOrNoDeviceError(err)
}
state := make([]byte, 2)
if _, err := fd.Read(state); err != nil {
// If the cgroup path is deleted at this point, then we just treat the freezer as
// being in an "undefined" state and ignore the error.
return cgroups.Undefined, ignoreNotExistOrNoDeviceError(err)
}
switch string(state) {
case "0\n":
return cgroups.Thawed, nil
case "1\n":
return waitFrozen(dirPath)
default:
return cgroups.Undefined, fmt.Errorf(`unknown "cgroup.freeze" state: %q`, state)
}
}
// ignoreNotExistOrNoDeviceError checks if the error is either a "not exist" error
// or a "no device" error, and returns nil in those cases. Otherwise, it returns the error.
func ignoreNotExistOrNoDeviceError(err error) error {
// We can safely ignore the error in the following two common situations:
// 1. The cgroup path does not exist at the time of opening(eg: the kernel is too old)
// — indicated by os.IsNotExist.
// 2. The cgroup path is deleted during the seek/read operation — indicated by
// errors.Is(err, unix.ENODEV).
// These conditions are expected and do not require special handling.
if os.IsNotExist(err) || errors.Is(err, unix.ENODEV) {
return nil
}
return err
}
// waitFrozen polls cgroup.events until it sees "frozen 1" in it.
func waitFrozen(dirPath string) (cgroups.FreezerState, error) {
fd, err := cgroups.OpenFile(dirPath, "cgroup.events", unix.O_RDONLY)
if err != nil {
return cgroups.Undefined, err
}
defer fd.Close()
// XXX: Simple wait/read/retry is used here. An implementation
// based on poll(2) or inotify(7) is possible, but it makes the code
// much more complicated. Maybe address this later.
const (
// Perform maxIter with waitTime in between iterations.
waitTime = 10 * time.Millisecond
maxIter = 1000
)
scanner := bufio.NewScanner(fd)
for i := 0; scanner.Scan(); {
if i == maxIter {
return cgroups.Undefined, fmt.Errorf("timeout of %s reached waiting for the cgroup to freeze", waitTime*maxIter)
}
if val, ok := strings.CutPrefix(scanner.Text(), "frozen "); ok {
if val[0] == '1' {
return cgroups.Frozen, nil
}
i++
// wait, then re-read
time.Sleep(waitTime)
_, err := fd.Seek(0, 0)
if err != nil {
return cgroups.Undefined, err
}
}
}
// Should only reach here either on read error,
// or if the file does not contain "frozen " line.
return cgroups.Undefined, scanner.Err()
}