github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/spec/config_linux.go (about) 1 // +build linux 2 3 package createconfig 4 5 import ( 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "strconv" 11 "strings" 12 13 "github.com/containers/libpod/pkg/rootless" 14 "github.com/opencontainers/runc/libcontainer/configs" 15 "github.com/opencontainers/runc/libcontainer/devices" 16 spec "github.com/opencontainers/runtime-spec/specs-go" 17 "github.com/opencontainers/runtime-tools/generate" 18 "github.com/pkg/errors" 19 "github.com/sirupsen/logrus" 20 "golang.org/x/sys/unix" 21 ) 22 23 // Device transforms a libcontainer configs.Device to a specs.LinuxDevice object. 24 func Device(d *configs.Device) spec.LinuxDevice { 25 return spec.LinuxDevice{ 26 Type: string(d.Type), 27 Path: d.Path, 28 Major: d.Major, 29 Minor: d.Minor, 30 FileMode: fmPtr(int64(d.FileMode)), 31 UID: u32Ptr(int64(d.Uid)), 32 GID: u32Ptr(int64(d.Gid)), 33 } 34 } 35 36 // DevicesFromPath computes a list of devices 37 func DevicesFromPath(g *generate.Generator, devicePath string) error { 38 devs := strings.Split(devicePath, ":") 39 resolvedDevicePath := devs[0] 40 // check if it is a symbolic link 41 if src, err := os.Lstat(resolvedDevicePath); err == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink { 42 if linkedPathOnHost, err := filepath.EvalSymlinks(resolvedDevicePath); err == nil { 43 resolvedDevicePath = linkedPathOnHost 44 } 45 } 46 st, err := os.Stat(resolvedDevicePath) 47 if err != nil { 48 return errors.Wrapf(err, "cannot stat %s", devicePath) 49 } 50 if st.IsDir() { 51 found := false 52 src := resolvedDevicePath 53 dest := src 54 var devmode string 55 if len(devs) > 1 { 56 if len(devs[1]) > 0 && devs[1][0] == '/' { 57 dest = devs[1] 58 } else { 59 devmode = devs[1] 60 } 61 } 62 if len(devs) > 2 { 63 if devmode != "" { 64 return errors.Wrapf(unix.EINVAL, "invalid device specification %s", devicePath) 65 } 66 devmode = devs[2] 67 } 68 69 // mount the internal devices recursively 70 if err := filepath.Walk(resolvedDevicePath, func(dpath string, f os.FileInfo, e error) error { 71 72 if f.Mode()&os.ModeDevice == os.ModeDevice { 73 found = true 74 device := fmt.Sprintf("%s:%s", dpath, filepath.Join(dest, strings.TrimPrefix(dpath, src))) 75 if devmode != "" { 76 device = fmt.Sprintf("%s:%s", device, devmode) 77 } 78 if err := addDevice(g, device); err != nil { 79 return errors.Wrapf(err, "failed to add %s device", dpath) 80 } 81 } 82 return nil 83 }); err != nil { 84 return err 85 } 86 if !found { 87 return errors.Wrapf(unix.EINVAL, "no devices found in %s", devicePath) 88 } 89 return nil 90 } 91 92 return addDevice(g, strings.Join(append([]string{resolvedDevicePath}, devs[1:]...), ":")) 93 } 94 95 func deviceCgroupRules(g *generate.Generator, deviceCgroupRules []string) error { 96 for _, deviceCgroupRule := range deviceCgroupRules { 97 if err := validateDeviceCgroupRule(deviceCgroupRule); err != nil { 98 return err 99 } 100 ss := parseDeviceCgroupRule(deviceCgroupRule) 101 if len(ss[0]) != 5 { 102 return errors.Errorf("invalid device cgroup rule format: '%s'", deviceCgroupRule) 103 } 104 matches := ss[0] 105 var major, minor *int64 106 if matches[2] == "*" { 107 majorDev := int64(-1) 108 major = &majorDev 109 } else { 110 majorDev, err := strconv.ParseInt(matches[2], 10, 64) 111 if err != nil { 112 return errors.Errorf("invalid major value in device cgroup rule format: '%s'", deviceCgroupRule) 113 } 114 major = &majorDev 115 } 116 if matches[3] == "*" { 117 minorDev := int64(-1) 118 minor = &minorDev 119 } else { 120 minorDev, err := strconv.ParseInt(matches[2], 10, 64) 121 if err != nil { 122 return errors.Errorf("invalid major value in device cgroup rule format: '%s'", deviceCgroupRule) 123 } 124 minor = &minorDev 125 } 126 g.AddLinuxResourcesDevice(true, matches[1], major, minor, matches[4]) 127 } 128 return nil 129 } 130 131 func addDevice(g *generate.Generator, device string) error { 132 src, dst, permissions, err := ParseDevice(device) 133 if err != nil { 134 return err 135 } 136 dev, err := devices.DeviceFromPath(src, permissions) 137 if err != nil { 138 return errors.Wrapf(err, "%s is not a valid device", src) 139 } 140 if rootless.IsRootless() { 141 if _, err := os.Stat(src); err != nil { 142 if os.IsNotExist(err) { 143 return errors.Wrapf(err, "the specified device %s doesn't exist", src) 144 } 145 return errors.Wrapf(err, "stat device %s exist", src) 146 } 147 perm := "ro" 148 if strings.Contains(permissions, "w") { 149 perm = "rw" 150 } 151 devMnt := spec.Mount{ 152 Destination: dst, 153 Type: TypeBind, 154 Source: src, 155 Options: []string{"slave", "nosuid", "noexec", perm, "rbind"}, 156 } 157 g.Config.Mounts = append(g.Config.Mounts, devMnt) 158 return nil 159 } 160 dev.Path = dst 161 linuxdev := spec.LinuxDevice{ 162 Path: dev.Path, 163 Type: string(dev.Type), 164 Major: dev.Major, 165 Minor: dev.Minor, 166 FileMode: &dev.FileMode, 167 UID: &dev.Uid, 168 GID: &dev.Gid, 169 } 170 g.AddDevice(linuxdev) 171 g.AddLinuxResourcesDevice(true, string(dev.Type), &dev.Major, &dev.Minor, dev.Permissions) 172 return nil 173 } 174 175 // based on getDevices from runc (libcontainer/devices/devices.go) 176 func getDevices(path string) ([]*configs.Device, error) { 177 files, err := ioutil.ReadDir(path) 178 if err != nil { 179 if rootless.IsRootless() && os.IsPermission(err) { 180 return nil, nil 181 } 182 return nil, err 183 } 184 out := []*configs.Device{} 185 for _, f := range files { 186 switch { 187 case f.IsDir(): 188 switch f.Name() { 189 // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 190 case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts": 191 continue 192 default: 193 sub, err := getDevices(filepath.Join(path, f.Name())) 194 if err != nil { 195 return nil, err 196 } 197 if sub != nil { 198 out = append(out, sub...) 199 } 200 continue 201 } 202 case f.Name() == "console": 203 continue 204 } 205 device, err := devices.DeviceFromPath(filepath.Join(path, f.Name()), "rwm") 206 if err != nil { 207 if err == devices.ErrNotADevice { 208 continue 209 } 210 if os.IsNotExist(err) { 211 continue 212 } 213 return nil, err 214 } 215 out = append(out, device) 216 } 217 return out, nil 218 } 219 220 func addPrivilegedDevices(g *generate.Generator) error { 221 hostDevices, err := getDevices("/dev") 222 if err != nil { 223 return err 224 } 225 g.ClearLinuxDevices() 226 227 if rootless.IsRootless() { 228 mounts := make(map[string]interface{}) 229 for _, m := range g.Mounts() { 230 mounts[m.Destination] = true 231 } 232 newMounts := []spec.Mount{} 233 for _, d := range hostDevices { 234 devMnt := spec.Mount{ 235 Destination: d.Path, 236 Type: TypeBind, 237 Source: d.Path, 238 Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"}, 239 } 240 if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") { 241 continue 242 } 243 if _, found := mounts[d.Path]; found { 244 continue 245 } 246 st, err := os.Stat(d.Path) 247 if err != nil { 248 if err == unix.EPERM { 249 continue 250 } 251 return errors.Wrapf(err, "stat %s", d.Path) 252 } 253 // Skip devices that the user has not access to. 254 if st.Mode()&0007 == 0 { 255 continue 256 } 257 newMounts = append(newMounts, devMnt) 258 } 259 g.Config.Mounts = append(newMounts, g.Config.Mounts...) 260 g.Config.Linux.Resources.Devices = nil 261 } else { 262 for _, d := range hostDevices { 263 g.AddDevice(Device(d)) 264 } 265 // Add resources device - need to clear the existing one first. 266 g.Config.Linux.Resources.Devices = nil 267 g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") 268 } 269 270 return nil 271 } 272 273 func (c *CreateConfig) createBlockIO() (*spec.LinuxBlockIO, error) { 274 var ret *spec.LinuxBlockIO 275 bio := &spec.LinuxBlockIO{} 276 if c.Resources.BlkioWeight > 0 { 277 ret = bio 278 bio.Weight = &c.Resources.BlkioWeight 279 } 280 if len(c.Resources.BlkioWeightDevice) > 0 { 281 var lwds []spec.LinuxWeightDevice 282 ret = bio 283 for _, i := range c.Resources.BlkioWeightDevice { 284 wd, err := ValidateweightDevice(i) 285 if err != nil { 286 return ret, errors.Wrapf(err, "invalid values for blkio-weight-device") 287 } 288 wdStat, err := GetStatFromPath(wd.Path) 289 if err != nil { 290 return ret, errors.Wrapf(err, "error getting stat from path %q", wd.Path) 291 } 292 lwd := spec.LinuxWeightDevice{ 293 Weight: &wd.Weight, 294 } 295 lwd.Major = int64(unix.Major(wdStat.Rdev)) 296 lwd.Minor = int64(unix.Minor(wdStat.Rdev)) 297 lwds = append(lwds, lwd) 298 } 299 bio.WeightDevice = lwds 300 } 301 if len(c.Resources.DeviceReadBps) > 0 { 302 ret = bio 303 readBps, err := makeThrottleArray(c.Resources.DeviceReadBps, bps) 304 if err != nil { 305 return ret, err 306 } 307 bio.ThrottleReadBpsDevice = readBps 308 } 309 if len(c.Resources.DeviceWriteBps) > 0 { 310 ret = bio 311 writeBpds, err := makeThrottleArray(c.Resources.DeviceWriteBps, bps) 312 if err != nil { 313 return ret, err 314 } 315 bio.ThrottleWriteBpsDevice = writeBpds 316 } 317 if len(c.Resources.DeviceReadIOps) > 0 { 318 ret = bio 319 readIOps, err := makeThrottleArray(c.Resources.DeviceReadIOps, iops) 320 if err != nil { 321 return ret, err 322 } 323 bio.ThrottleReadIOPSDevice = readIOps 324 } 325 if len(c.Resources.DeviceWriteIOps) > 0 { 326 ret = bio 327 writeIOps, err := makeThrottleArray(c.Resources.DeviceWriteIOps, iops) 328 if err != nil { 329 return ret, err 330 } 331 bio.ThrottleWriteIOPSDevice = writeIOps 332 } 333 return ret, nil 334 } 335 336 func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrottleDevice, error) { 337 var ( 338 ltds []spec.LinuxThrottleDevice 339 t *throttleDevice 340 err error 341 ) 342 for _, i := range throttleInput { 343 if rateType == bps { 344 t, err = validateBpsDevice(i) 345 } else { 346 t, err = validateIOpsDevice(i) 347 } 348 if err != nil { 349 return []spec.LinuxThrottleDevice{}, err 350 } 351 ltdStat, err := GetStatFromPath(t.path) 352 if err != nil { 353 return ltds, errors.Wrapf(err, "error getting stat from path %q", t.path) 354 } 355 ltd := spec.LinuxThrottleDevice{ 356 Rate: t.rate, 357 } 358 ltd.Major = int64(unix.Major(ltdStat.Rdev)) 359 ltd.Minor = int64(unix.Minor(ltdStat.Rdev)) 360 ltds = append(ltds, ltd) 361 } 362 return ltds, nil 363 } 364 365 func GetStatFromPath(path string) (unix.Stat_t, error) { 366 s := unix.Stat_t{} 367 err := unix.Stat(path, &s) 368 return s, err 369 } 370 371 func getNOFILESettings() (uint64, uint64) { 372 if rootless.IsRootless() { 373 var rlimit unix.Rlimit 374 if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err == nil { 375 return rlimit.Cur, rlimit.Max 376 } else { 377 logrus.Warnf("failed to return RLIMIT_NOFILE ulimit %q", err) 378 } 379 } 380 return kernelMax, kernelMax 381 } 382 383 func getNPROCSettings() (uint64, uint64) { 384 if rootless.IsRootless() { 385 var rlimit unix.Rlimit 386 if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err == nil { 387 return rlimit.Cur, rlimit.Max 388 } else { 389 logrus.Warnf("failed to return RLIMIT_NPROC ulimit %q", err) 390 } 391 } 392 return kernelMax, kernelMax 393 }