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  }