github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/utils/utils_supported.go (about)

     1  // +build linux darwin
     2  
     3  package utils
     4  
     5  import (
     6  	"bufio"
     7  	"bytes"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/containers/podman/v2/pkg/cgroups"
    15  	"github.com/containers/podman/v2/pkg/rootless"
    16  	systemdDbus "github.com/coreos/go-systemd/v22/dbus"
    17  	"github.com/godbus/dbus/v5"
    18  	"github.com/pkg/errors"
    19  	"github.com/sirupsen/logrus"
    20  )
    21  
    22  // RunUnderSystemdScope adds the specified pid to a systemd scope
    23  func RunUnderSystemdScope(pid int, slice string, unitName string) error {
    24  	var properties []systemdDbus.Property
    25  	var conn *systemdDbus.Conn
    26  	var err error
    27  
    28  	if rootless.IsRootless() {
    29  		conn, err = cgroups.GetUserConnection(rootless.GetRootlessUID())
    30  		if err != nil {
    31  			return err
    32  		}
    33  	} else {
    34  		conn, err = systemdDbus.New()
    35  		if err != nil {
    36  			return err
    37  		}
    38  	}
    39  	properties = append(properties, systemdDbus.PropSlice(slice))
    40  	properties = append(properties, newProp("PIDs", []uint32{uint32(pid)}))
    41  	properties = append(properties, newProp("Delegate", true))
    42  	properties = append(properties, newProp("DefaultDependencies", false))
    43  	ch := make(chan string)
    44  	_, err = conn.StartTransientUnit(unitName, "replace", properties, ch)
    45  	if err != nil {
    46  		// On errors check if the cgroup already exists, if it does move the process there
    47  		if props, err := conn.GetUnitTypeProperties(unitName, "Scope"); err == nil {
    48  			if cgroup, ok := props["ControlGroup"].(string); ok && cgroup != "" {
    49  				if err := moveUnderCgroup(cgroup, "", []uint32{uint32(pid)}); err != nil {
    50  					return err
    51  				}
    52  				return nil
    53  			}
    54  		}
    55  		return err
    56  	}
    57  	defer conn.Close()
    58  
    59  	// Block until job is started
    60  	<-ch
    61  
    62  	return nil
    63  }
    64  
    65  func getCgroupProcess(procFile string) (string, error) {
    66  	f, err := os.Open(procFile)
    67  	if err != nil {
    68  		return "", err
    69  	}
    70  	defer f.Close()
    71  
    72  	scanner := bufio.NewScanner(f)
    73  	cgroup := "/"
    74  	for scanner.Scan() {
    75  		line := scanner.Text()
    76  		parts := strings.SplitN(line, ":", 3)
    77  		if len(parts) != 3 {
    78  			return "", errors.Errorf("cannot parse cgroup line %q", line)
    79  		}
    80  		if strings.HasPrefix(line, "0::") {
    81  			cgroup = line[3:]
    82  			break
    83  		}
    84  		// root cgroup, skip it
    85  		if parts[2] == "/" {
    86  			continue
    87  		}
    88  		// The process must have the same cgroup path for all controllers
    89  		// The OCI runtime spec file allow us to specify only one path.
    90  		if cgroup != "/" && cgroup != parts[2] {
    91  			return "", errors.Errorf("cgroup configuration not supported, the process is in two different cgroups")
    92  		}
    93  		cgroup = parts[2]
    94  	}
    95  	if cgroup == "/" {
    96  		return "", errors.Errorf("could not find cgroup mount in %q", procFile)
    97  	}
    98  	return cgroup, nil
    99  }
   100  
   101  // GetOwnCgroup returns the cgroup for the current process.
   102  func GetOwnCgroup() (string, error) {
   103  	return getCgroupProcess("/proc/self/cgroup")
   104  }
   105  
   106  // GetCgroupProcess returns the cgroup for the specified process process.
   107  func GetCgroupProcess(pid int) (string, error) {
   108  	return getCgroupProcess(fmt.Sprintf("/proc/%d/cgroup", pid))
   109  }
   110  
   111  // MoveUnderCgroupSubtree moves the PID under a cgroup subtree.
   112  func MoveUnderCgroupSubtree(subtree string) error {
   113  	return moveUnderCgroup("", subtree, nil)
   114  }
   115  
   116  // moveUnderCgroup moves a group of processes to a new cgroup.
   117  // If cgroup is the empty string, then the current calling process cgroup is used.
   118  // If processes is empty, then the processes from the current cgroup are moved.
   119  func moveUnderCgroup(cgroup, subtree string, processes []uint32) error {
   120  	procFile := "/proc/self/cgroup"
   121  	f, err := os.Open(procFile)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	defer f.Close()
   126  
   127  	unifiedMode, err := cgroups.IsCgroup2UnifiedMode()
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	scanner := bufio.NewScanner(f)
   133  	for scanner.Scan() {
   134  		line := scanner.Text()
   135  		parts := strings.SplitN(line, ":", 3)
   136  		if len(parts) != 3 {
   137  			return errors.Errorf("cannot parse cgroup line %q", line)
   138  		}
   139  
   140  		// root cgroup, skip it
   141  		if parts[2] == "/" {
   142  			continue
   143  		}
   144  
   145  		cgroupRoot := "/sys/fs/cgroup"
   146  		// Special case the unified mount on hybrid cgroup and named hierarchies.
   147  		// This works on Fedora 31, but we should really parse the mounts to see
   148  		// where the cgroup hierarchy is mounted.
   149  		if parts[1] == "" && !unifiedMode {
   150  			// If it is not using unified mode, the cgroup v2 hierarchy is
   151  			// usually mounted under /sys/fs/cgroup/unified
   152  			cgroupRoot = filepath.Join(cgroupRoot, "unified")
   153  		} else if parts[1] != "" {
   154  			// Assume the controller is mounted at /sys/fs/cgroup/$CONTROLLER.
   155  			controller := strings.TrimPrefix(parts[1], "name=")
   156  			cgroupRoot = filepath.Join(cgroupRoot, controller)
   157  		}
   158  
   159  		parentCgroup := cgroup
   160  		if parentCgroup == "" {
   161  			parentCgroup = parts[2]
   162  		}
   163  		newCgroup := filepath.Join(cgroupRoot, parentCgroup, subtree)
   164  		if err := os.Mkdir(newCgroup, 0755); err != nil && !os.IsExist(err) {
   165  			return err
   166  		}
   167  
   168  		f, err := os.OpenFile(filepath.Join(newCgroup, "cgroup.procs"), os.O_RDWR, 0755)
   169  		if err != nil {
   170  			return err
   171  		}
   172  		defer f.Close()
   173  
   174  		if len(processes) > 0 {
   175  			for _, pid := range processes {
   176  				if _, err := f.Write([]byte(fmt.Sprintf("%d\n", pid))); err != nil {
   177  					logrus.Warnf("Cannot move process %d to cgroup %q", pid, newCgroup)
   178  				}
   179  			}
   180  		} else {
   181  			processesData, err := ioutil.ReadFile(filepath.Join(cgroupRoot, parts[2], "cgroup.procs"))
   182  			if err != nil {
   183  				return err
   184  			}
   185  			for _, pid := range bytes.Split(processesData, []byte("\n")) {
   186  				if _, err := f.Write(pid); err != nil {
   187  					logrus.Warnf("Cannot move process %s to cgroup %q", string(pid), newCgroup)
   188  				}
   189  			}
   190  		}
   191  	}
   192  	return nil
   193  }
   194  
   195  func newProp(name string, units interface{}) systemdDbus.Property {
   196  	return systemdDbus.Property{
   197  		Name:  name,
   198  		Value: dbus.MakeVariant(units),
   199  	}
   200  }