| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- package fs2
- import (
- "bufio"
- "errors"
- "fmt"
- "os"
- "strings"
- "time"
- "golang.org/x/sys/unix"
- "github.com/opencontainers/runc/libcontainer/cgroups"
- "github.com/opencontainers/runc/libcontainer/configs"
- )
- func setFreezer(dirPath string, state configs.FreezerState) error {
- var stateStr string
- switch state {
- case configs.Undefined:
- return nil
- case configs.Frozen:
- stateStr = "1"
- case configs.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 != configs.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) (configs.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.
- if os.IsNotExist(err) || errors.Is(err, unix.ENODEV) {
- err = nil
- }
- return configs.Undefined, err
- }
- defer fd.Close()
- return readFreezer(dirPath, fd)
- }
- func readFreezer(dirPath string, fd *os.File) (configs.FreezerState, error) {
- if _, err := fd.Seek(0, 0); err != nil {
- return configs.Undefined, err
- }
- state := make([]byte, 2)
- if _, err := fd.Read(state); err != nil {
- return configs.Undefined, err
- }
- switch string(state) {
- case "0\n":
- return configs.Thawed, nil
- case "1\n":
- return waitFrozen(dirPath)
- default:
- return configs.Undefined, fmt.Errorf(`unknown "cgroup.freeze" state: %q`, state)
- }
- }
- // waitFrozen polls cgroup.events until it sees "frozen 1" in it.
- func waitFrozen(dirPath string) (configs.FreezerState, error) {
- fd, err := cgroups.OpenFile(dirPath, "cgroup.events", unix.O_RDONLY)
- if err != nil {
- return configs.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 configs.Undefined, fmt.Errorf("timeout of %s reached waiting for the cgroup to freeze", waitTime*maxIter)
- }
- line := scanner.Text()
- val := strings.TrimPrefix(line, "frozen ")
- if val != line { // got prefix
- if val[0] == '1' {
- return configs.Frozen, nil
- }
- i++
- // wait, then re-read
- time.Sleep(waitTime)
- _, err := fd.Seek(0, 0)
- if err != nil {
- return configs.Undefined, err
- }
- }
- }
- // Should only reach here either on read error,
- // or if the file does not contain "frozen " line.
- return configs.Undefined, scanner.Err()
- }
|