1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
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()
}
|