github.com/criyle/go-sandbox@v0.10.3/pkg/cgroup/utils_linux.go (about)

     1  package cgroup
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/fs"
     7  	"math/rand/v2"
     8  	"os"
     9  	"path"
    10  	"strconv"
    11  	"strings"
    12  	"syscall"
    13  
    14  	"golang.org/x/sys/unix"
    15  )
    16  
    17  // EnsureDirExists creates directories if the path not exists
    18  func EnsureDirExists(path string) error {
    19  	if _, err := os.Stat(path); os.IsNotExist(err) {
    20  		return os.MkdirAll(path, dirPerm)
    21  	}
    22  	return os.ErrExist
    23  }
    24  
    25  // CreateV1ControllerPath create path for controller with given group, prefix
    26  func CreateV1ControllerPath(controller, prefix string) (string, error) {
    27  	p := path.Join(basePath, controller, prefix)
    28  	return p, EnsureDirExists(p)
    29  }
    30  
    31  const initPath = "init"
    32  
    33  // EnableV2Nesting migrates all process in the container to nested /init path
    34  // and enables all available controllers in the root cgroup
    35  func EnableV2Nesting() error {
    36  	if DetectType() != TypeV2 {
    37  		return nil
    38  	}
    39  
    40  	p, err := readFile(path.Join(basePath, cgroupProcs))
    41  	if err != nil {
    42  		return err
    43  	}
    44  	procs := strings.Split(string(p), "\n")
    45  	if len(procs) == 0 {
    46  		return nil
    47  	}
    48  
    49  	// mkdir init
    50  	if err := os.Mkdir(path.Join(basePath, initPath), dirPerm); err != nil && !errors.Is(err, os.ErrExist) {
    51  		return err
    52  	}
    53  	// move all process into init cgroup
    54  	procFile, err := os.OpenFile(path.Join(basePath, initPath, cgroupProcs), os.O_RDWR, filePerm)
    55  	if err != nil {
    56  		return err
    57  	}
    58  	for _, v := range procs {
    59  		if _, err := procFile.WriteString(v); err != nil {
    60  			continue
    61  			//return err
    62  		}
    63  	}
    64  	procFile.Close()
    65  	return nil
    66  }
    67  
    68  // ReadProcesses reads cgroup.procs file and return pids individually
    69  func ReadProcesses(path string) ([]int, error) {
    70  	content, err := readFile(path)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	procs := strings.Split(string(content), "\n")
    75  	rt := make([]int, len(procs))
    76  	for i, x := range procs {
    77  		if len(x) == 0 {
    78  			continue
    79  		}
    80  		rt[i], err = strconv.Atoi(x)
    81  		if err != nil {
    82  			return nil, err
    83  		}
    84  	}
    85  	return rt, nil
    86  }
    87  
    88  // AddProcesses add processes into cgroup.procs file
    89  func AddProcesses(path string, procs []int) error {
    90  	f, err := os.OpenFile(path, os.O_RDWR, filePerm)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	defer f.Close()
    95  	for _, p := range procs {
    96  		if _, err := f.WriteString(strconv.Itoa(p)); err != nil {
    97  			return err
    98  		}
    99  	}
   100  	return nil
   101  }
   102  
   103  // DetectType detects current mounted cgroup type in systemd default path
   104  func DetectType() Type {
   105  	// if /sys/fs/cgroup is mounted as CGROUPV2 or TMPFS (V1)
   106  	var st unix.Statfs_t
   107  	if err := unix.Statfs(basePath, &st); err != nil {
   108  		// ignore errors, defaulting to CgroupV1
   109  		return TypeV1
   110  	}
   111  	if st.Type == unix.CGROUP2_SUPER_MAGIC {
   112  		return TypeV2
   113  	}
   114  	return TypeV1
   115  }
   116  
   117  func remove(name string) error {
   118  	if name != "" {
   119  		return os.Remove(name)
   120  	}
   121  	return nil
   122  }
   123  
   124  var errPatternHasSeparator = errors.New("pattern contains path separator")
   125  
   126  // prefixAndSuffix splits pattern by the last wildcard "*", if applicable,
   127  // returning prefix as the part before "*" and suffix as the part after "*".
   128  func prefixAndSuffix(pattern string) (prefix, suffix string, err error) {
   129  	for i := 0; i < len(pattern); i++ {
   130  		if os.IsPathSeparator(pattern[i]) {
   131  			return "", "", errPatternHasSeparator
   132  		}
   133  	}
   134  	if pos := strings.LastIndexByte(pattern, '*'); pos != -1 {
   135  		prefix, suffix = pattern[:pos], pattern[pos+1:]
   136  	} else {
   137  		prefix = pattern
   138  	}
   139  	return prefix, suffix, nil
   140  }
   141  
   142  func readFile(p string) ([]byte, error) {
   143  	data, err := os.ReadFile(p)
   144  	for err != nil && errors.Is(err, syscall.EINTR) {
   145  		data, err = os.ReadFile(p)
   146  	}
   147  	return data, err
   148  }
   149  
   150  func writeFile(p string, content []byte, perm fs.FileMode) error {
   151  	err := os.WriteFile(p, content, perm)
   152  	for err != nil && errors.Is(err, syscall.EINTR) {
   153  		err = os.WriteFile(p, content, perm)
   154  	}
   155  	return err
   156  }
   157  
   158  func nextRandom() string {
   159  	return strconv.Itoa(int(rand.Int32()))
   160  }
   161  
   162  // randomBuild creates a cgroup with random directory, similar to os.MkdirTemp
   163  func randomBuild(pattern string, build func(string) (Cgroup, error)) (Cgroup, error) {
   164  	prefix, suffix, err := prefixAndSuffix(pattern)
   165  	if err != nil {
   166  		return nil, fmt.Errorf("cgroup.builder: random %v", err)
   167  	}
   168  
   169  	try := 0
   170  	for {
   171  		name := prefix + nextRandom() + suffix
   172  		cg, err := build(name)
   173  		if err == nil {
   174  			return cg, nil
   175  		}
   176  		if errors.Is(err, os.ErrExist) || cg.Existing() {
   177  			if try++; try < 10000 {
   178  				continue
   179  			}
   180  			return nil, fmt.Errorf("cgroup.builder: tried 10000 times but failed")
   181  		}
   182  		return nil, fmt.Errorf("cgroup.builder: random %v", err)
   183  	}
   184  }