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