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  }