...

Source file src/github.com/opencontainers/runc/libcontainer/cgroups/fs2/freezer.go

Documentation: github.com/opencontainers/runc/libcontainer/cgroups/fs2

     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  }
   128  

View as plain text