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