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 }