| 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() |
| } |