github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/common/cgroup_util.go (about)

     1  // Copyright 2014 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //+build linux
    16  
    17  package common
    18  
    19  // adapted from systemd/src/shared/cgroup-util.c
    20  // TODO this should be moved to go-systemd
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  	"regexp"
    27  	"strings"
    28  )
    29  
    30  const (
    31  	unitNameMax = 256
    32  )
    33  
    34  var (
    35  	validChars = regexp.MustCompile(`[a-zA-Z0-9:\-_\.\\]+`)
    36  )
    37  
    38  // cgEscape implements very minimal escaping for names to be used as file names
    39  // in the cgroup tree: any name which might conflict with a kernel name or is
    40  // prefixed with '_' is prefixed with a '_'. That way, when reading cgroup
    41  // names it is sufficient to remove a single prefixing underscore if there is
    42  // one.
    43  func cgEscape(p string) string {
    44  	needPrefix := false
    45  
    46  	switch {
    47  	case strings.HasPrefix(p, "_"):
    48  		fallthrough
    49  	case strings.HasPrefix(p, "."):
    50  		fallthrough
    51  	case p == "notify_on_release":
    52  		fallthrough
    53  	case p == "release_agent":
    54  		fallthrough
    55  	case p == "tasks":
    56  		needPrefix = true
    57  	case strings.Contains(p, "."):
    58  		sp := strings.Split(p, ".")
    59  		if sp[0] == "cgroup" {
    60  			needPrefix = true
    61  		} else {
    62  			n := sp[0]
    63  			if checkHierarchy(n) {
    64  				needPrefix = true
    65  			}
    66  		}
    67  	}
    68  
    69  	if needPrefix {
    70  		return "_" + p
    71  	}
    72  
    73  	return p
    74  }
    75  
    76  func filenameIsValid(p string) bool {
    77  	switch {
    78  	case p == "", p == ".", p == "..", strings.Contains(p, "/"):
    79  		return false
    80  	default:
    81  		return true
    82  	}
    83  }
    84  
    85  func checkHierarchy(p string) bool {
    86  	if !filenameIsValid(p) {
    87  		return true
    88  	}
    89  
    90  	cc := filepath.Join("/sys/fs/cgroup", p)
    91  	if _, err := os.Stat(cc); os.IsNotExist(err) {
    92  		return false
    93  	}
    94  
    95  	return true
    96  }
    97  
    98  func cgUnescape(p string) string {
    99  	if p[0] == '_' {
   100  		return p[1:]
   101  	}
   102  
   103  	return p
   104  }
   105  
   106  func sliceNameIsValid(n string) bool {
   107  	if n == "" {
   108  		return false
   109  	}
   110  
   111  	if len(n) >= unitNameMax {
   112  		return false
   113  	}
   114  
   115  	if !strings.Contains(n, ".") {
   116  		return false
   117  	}
   118  
   119  	if validChars.FindString(n) != n {
   120  		return false
   121  	}
   122  
   123  	if strings.Contains(n, "@") {
   124  		return false
   125  	}
   126  
   127  	return true
   128  }
   129  
   130  // SliceToPath explodes a slice name to its corresponding path in the cgroup
   131  // hierarchy. For example, a slice named "foo-bar-baz.slice" corresponds to the
   132  // path "foo.slice/foo-bar.slice/foo-bar-baz.slice". See systemd.slice(5)
   133  func SliceToPath(unit string) (string, error) {
   134  	if unit == "-.slice" {
   135  		return "", nil
   136  	}
   137  
   138  	if !strings.HasSuffix(unit, ".slice") {
   139  		return "", fmt.Errorf("not a slice")
   140  	}
   141  
   142  	if !sliceNameIsValid(unit) {
   143  		return "", fmt.Errorf("invalid slice name")
   144  	}
   145  
   146  	prefix := unitnameToPrefix(unit)
   147  
   148  	// don't allow initial dashes
   149  	if prefix[0] == '-' {
   150  		return "", fmt.Errorf("initial dash")
   151  	}
   152  
   153  	prefixParts := strings.Split(prefix, "-")
   154  
   155  	var curSlice string
   156  	var slicePath string
   157  	for _, slicePart := range prefixParts {
   158  		if slicePart == "" {
   159  			return "", fmt.Errorf("trailing or double dash")
   160  		}
   161  
   162  		if curSlice != "" {
   163  			curSlice = curSlice + "-"
   164  		}
   165  		curSlice = curSlice + slicePart
   166  
   167  		curSliceDir := curSlice + ".slice"
   168  		escaped := cgEscape(curSliceDir)
   169  
   170  		slicePath = filepath.Join(slicePath, escaped)
   171  	}
   172  
   173  	return slicePath, nil
   174  }
   175  
   176  func unitnameToPrefix(unit string) string {
   177  	idx := strings.Index(unit, "@")
   178  	if idx == -1 {
   179  		idx = strings.LastIndex(unit, ".")
   180  	}
   181  
   182  	return unit[:idx]
   183  }