freezer.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. package fs2
  2. import (
  3. "bufio"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "strings"
  8. "time"
  9. "golang.org/x/sys/unix"
  10. "github.com/opencontainers/runc/libcontainer/cgroups"
  11. "github.com/opencontainers/runc/libcontainer/configs"
  12. )
  13. func setFreezer(dirPath string, state configs.FreezerState) error {
  14. var stateStr string
  15. switch state {
  16. case configs.Undefined:
  17. return nil
  18. case configs.Frozen:
  19. stateStr = "1"
  20. case configs.Thawed:
  21. stateStr = "0"
  22. default:
  23. return fmt.Errorf("invalid freezer state %q requested", state)
  24. }
  25. fd, err := cgroups.OpenFile(dirPath, "cgroup.freeze", unix.O_RDWR)
  26. if err != nil {
  27. // We can ignore this request as long as the user didn't ask us to
  28. // freeze the container (since without the freezer cgroup, that's a
  29. // no-op).
  30. if state != configs.Frozen {
  31. return nil
  32. }
  33. return fmt.Errorf("freezer not supported: %w", err)
  34. }
  35. defer fd.Close()
  36. if _, err := fd.WriteString(stateStr); err != nil {
  37. return err
  38. }
  39. // Confirm that the cgroup did actually change states.
  40. if actualState, err := readFreezer(dirPath, fd); err != nil {
  41. return err
  42. } else if actualState != state {
  43. return fmt.Errorf(`expected "cgroup.freeze" to be in state %q but was in %q`, state, actualState)
  44. }
  45. return nil
  46. }
  47. func getFreezer(dirPath string) (configs.FreezerState, error) {
  48. fd, err := cgroups.OpenFile(dirPath, "cgroup.freeze", unix.O_RDONLY)
  49. if err != nil {
  50. // If the kernel is too old, then we just treat the freezer as being in
  51. // an "undefined" state.
  52. if os.IsNotExist(err) || errors.Is(err, unix.ENODEV) {
  53. err = nil
  54. }
  55. return configs.Undefined, err
  56. }
  57. defer fd.Close()
  58. return readFreezer(dirPath, fd)
  59. }
  60. func readFreezer(dirPath string, fd *os.File) (configs.FreezerState, error) {
  61. if _, err := fd.Seek(0, 0); err != nil {
  62. return configs.Undefined, err
  63. }
  64. state := make([]byte, 2)
  65. if _, err := fd.Read(state); err != nil {
  66. return configs.Undefined, err
  67. }
  68. switch string(state) {
  69. case "0\n":
  70. return configs.Thawed, nil
  71. case "1\n":
  72. return waitFrozen(dirPath)
  73. default:
  74. return configs.Undefined, fmt.Errorf(`unknown "cgroup.freeze" state: %q`, state)
  75. }
  76. }
  77. // waitFrozen polls cgroup.events until it sees "frozen 1" in it.
  78. func waitFrozen(dirPath string) (configs.FreezerState, error) {
  79. fd, err := cgroups.OpenFile(dirPath, "cgroup.events", unix.O_RDONLY)
  80. if err != nil {
  81. return configs.Undefined, err
  82. }
  83. defer fd.Close()
  84. // XXX: Simple wait/read/retry is used here. An implementation
  85. // based on poll(2) or inotify(7) is possible, but it makes the code
  86. // much more complicated. Maybe address this later.
  87. const (
  88. // Perform maxIter with waitTime in between iterations.
  89. waitTime = 10 * time.Millisecond
  90. maxIter = 1000
  91. )
  92. scanner := bufio.NewScanner(fd)
  93. for i := 0; scanner.Scan(); {
  94. if i == maxIter {
  95. return configs.Undefined, fmt.Errorf("timeout of %s reached waiting for the cgroup to freeze", waitTime*maxIter)
  96. }
  97. line := scanner.Text()
  98. val := strings.TrimPrefix(line, "frozen ")
  99. if val != line { // got prefix
  100. if val[0] == '1' {
  101. return configs.Frozen, nil
  102. }
  103. i++
  104. // wait, then re-read
  105. time.Sleep(waitTime)
  106. _, err := fd.Seek(0, 0)
  107. if err != nil {
  108. return configs.Undefined, err
  109. }
  110. }
  111. }
  112. // Should only reach here either on read error,
  113. // or if the file does not contain "frozen " line.
  114. return configs.Undefined, scanner.Err()
  115. }