Merge pull request #19483 from dfr/freebsd-devices

pkg/specgen: Add device support for FreeBSD
This commit is contained in:
OpenShift Merge Robot
2023-08-04 14:35:33 +02:00
committed by GitHub
7 changed files with 245 additions and 77 deletions

View File

@ -0,0 +1,64 @@
//go:build linux || ignore || freebsd
// +build linux ignore freebsd
package generate
import (
"fmt"
"strings"
)
// ParseDevice parses device mapping string to a src, dest & permissions string
func ParseDevice(device string) (string, string, string, error) {
var src string
var dst string
permissions := "rwm"
arr := strings.Split(device, ":")
switch len(arr) {
case 3:
if !IsValidDeviceMode(arr[2]) {
return "", "", "", fmt.Errorf("invalid device mode: %s", arr[2])
}
permissions = arr[2]
fallthrough
case 2:
if IsValidDeviceMode(arr[1]) {
permissions = arr[1]
} else {
if len(arr[1]) > 0 && arr[1][0] != '/' {
return "", "", "", fmt.Errorf("invalid device mode: %s", arr[1])
}
dst = arr[1]
}
fallthrough
case 1:
src = arr[0]
default:
return "", "", "", fmt.Errorf("invalid device specification: %s", device)
}
if dst == "" {
dst = src
}
return src, dst, permissions, nil
}
// IsValidDeviceMode checks if the mode for device is valid or not.
// IsValid mode is a composition of r (read), w (write), and m (mknod).
func IsValidDeviceMode(mode string) bool {
var legalDeviceMode = map[rune]bool{
'r': true,
'w': true,
'm': true,
}
if mode == "" {
return false
}
for _, c := range mode {
if !legalDeviceMode[c] {
return false
}
legalDeviceMode[c] = false
}
return true
}

View File

@ -0,0 +1,29 @@
package generate
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseDevice(t *testing.T) {
tests := []struct {
device string
src string
dst string
perm string
}{
{"/dev/foo", "/dev/foo", "/dev/foo", "rwm"},
{"/dev/foo:/dev/bar", "/dev/foo", "/dev/bar", "rwm"},
{"/dev/foo:/dev/bar:rw", "/dev/foo", "/dev/bar", "rw"},
{"/dev/foo:rw", "/dev/foo", "/dev/foo", "rw"},
{"/dev/foo::rw", "/dev/foo", "/dev/foo", "rw"},
}
for _, test := range tests {
src, dst, perm, err := ParseDevice(test.device)
assert.NoError(t, err)
assert.Equal(t, src, test.src)
assert.Equal(t, dst, test.dst)
assert.Equal(t, perm, test.perm)
}
}

View File

@ -0,0 +1,118 @@
package generate
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
"github.com/opencontainers/runtime-tools/generate"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
// DevicesFromPath computes a list of devices
func DevicesFromPath(g *generate.Generator, devicePath string) error {
if isCDIDevice(devicePath) {
registry := cdi.GetRegistry(
cdi.WithAutoRefresh(false),
)
if err := registry.Refresh(); err != nil {
logrus.Debugf("The following error was triggered when refreshing the CDI registry: %v", err)
}
_, err := registry.InjectDevices(g.Config, devicePath)
if err != nil {
return fmt.Errorf("setting up CDI devices: %w", err)
}
return nil
}
devs := strings.Split(devicePath, ":")
resolvedDevicePath := devs[0]
// check if it is a symbolic link
if src, err := os.Lstat(resolvedDevicePath); err == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink {
if linkedPathOnHost, err := filepath.EvalSymlinks(resolvedDevicePath); err == nil {
resolvedDevicePath = linkedPathOnHost
}
}
st, err := os.Stat(resolvedDevicePath)
if err != nil {
return err
}
if st.IsDir() {
// For devfs, we need to add the directory as well
addDevice(g, resolvedDevicePath)
found := false
src := resolvedDevicePath
dest := src
var devmode string
if len(devs) > 1 {
if len(devs[1]) > 0 && devs[1][0] == '/' {
dest = devs[1]
} else {
devmode = devs[1]
}
}
if len(devs) > 2 {
if devmode != "" {
return fmt.Errorf("invalid device specification %s: %w", devicePath, unix.EINVAL)
}
devmode = devs[2]
}
// mount the internal devices recursively
if err := filepath.WalkDir(resolvedDevicePath, func(dpath string, d fs.DirEntry, e error) error {
if d.Type()&os.ModeDevice == os.ModeDevice {
found = true
device := fmt.Sprintf("%s:%s", dpath, filepath.Join(dest, strings.TrimPrefix(dpath, src)))
if devmode != "" {
device = fmt.Sprintf("%s:%s", device, devmode)
}
if err := addDevice(g, device); err != nil {
return fmt.Errorf("failed to add %s device: %w", dpath, err)
}
}
return nil
}); err != nil {
return err
}
if !found {
return fmt.Errorf("no devices found in %s: %w", devicePath, unix.EINVAL)
}
return nil
}
return addDevice(g, strings.Join(append([]string{resolvedDevicePath}, devs[1:]...), ":"))
}
func addDevice(g *generate.Generator, device string) error {
src, dst, permissions, err := ParseDevice(device)
if err != nil {
return err
}
if src != dst {
return fmt.Errorf("container device must be the same as host device on FreeBSD")
}
mode := 0
if strings.Contains(permissions, "r") {
mode |= unix.S_IRUSR
}
if strings.Contains(permissions, "w") {
mode |= unix.S_IWUSR
}
// Find the devfs mount so that we can add rules to expose the device
for k, m := range g.Config.Mounts {
if m.Type == "devfs" {
if dev, ok := strings.CutPrefix(src, "/dev/"); ok {
m.Options = append(m.Options,
fmt.Sprintf("rule=path %s unhide mode %04o", dev, mode))
} else {
return fmt.Errorf("expected device to start with \"/dev\": %v", dev)
}
g.Config.Mounts[k] = m
return nil
}
}
return fmt.Errorf("devfs not found in generator")
}

View File

@ -176,61 +176,6 @@ func addDevice(g *generate.Generator, device string) error {
return nil
}
// ParseDevice parses device mapping string to a src, dest & permissions string
func ParseDevice(device string) (string, string, string, error) {
var src string
var dst string
permissions := "rwm"
arr := strings.Split(device, ":")
switch len(arr) {
case 3:
if !IsValidDeviceMode(arr[2]) {
return "", "", "", fmt.Errorf("invalid device mode: %s", arr[2])
}
permissions = arr[2]
fallthrough
case 2:
if IsValidDeviceMode(arr[1]) {
permissions = arr[1]
} else {
if len(arr[1]) > 0 && arr[1][0] != '/' {
return "", "", "", fmt.Errorf("invalid device mode: %s", arr[1])
}
dst = arr[1]
}
fallthrough
case 1:
src = arr[0]
default:
return "", "", "", fmt.Errorf("invalid device specification: %s", device)
}
if dst == "" {
dst = src
}
return src, dst, permissions, nil
}
// IsValidDeviceMode checks if the mode for device is valid or not.
// IsValid mode is a composition of r (read), w (write), and m (mknod).
func IsValidDeviceMode(mode string) bool {
var legalDeviceMode = map[rune]bool{
'r': true,
'w': true,
'm': true,
}
if mode == "" {
return false
}
for _, c := range mode {
if !legalDeviceMode[c] {
return false
}
legalDeviceMode[c] = false
}
return true
}
func supportAmbientCapabilities() bool {
err := unix.Prctl(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, 0, 0, 0)
return err == nil

View File

@ -26,25 +26,3 @@ func TestShouldMask(t *testing.T) {
assert.Equal(t, val, test.shouldMask)
}
}
func TestParseDevice(t *testing.T) {
tests := []struct {
device string
src string
dst string
perm string
}{
{"/dev/foo", "/dev/foo", "/dev/foo", "rwm"},
{"/dev/foo:/dev/bar", "/dev/foo", "/dev/bar", "rwm"},
{"/dev/foo:/dev/bar:rw", "/dev/foo", "/dev/bar", "rw"},
{"/dev/foo:rw", "/dev/foo", "/dev/foo", "rw"},
{"/dev/foo::rw", "/dev/foo", "/dev/foo", "rw"},
}
for _, test := range tests {
src, dst, perm, err := ParseDevice(test.device)
assert.NoError(t, err)
assert.Equal(t, src, test.src)
assert.Equal(t, dst, test.dst)
assert.Equal(t, perm, test.perm)
}
}

View File

@ -48,6 +48,28 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
g.AddAnnotation(key, val)
}
// Devices
var userDevices []spec.LinuxDevice
if !s.Privileged {
// add default devices from containers.conf
for _, device := range rtc.Containers.Devices {
if err = DevicesFromPath(&g, device); err != nil {
return nil, err
}
}
if len(compatibleOptions.HostDeviceList) > 0 && len(s.Devices) == 0 {
userDevices = compatibleOptions.HostDeviceList
} else {
userDevices = s.Devices
}
// add default devices specified by caller
for _, device := range userDevices {
if err = DevicesFromPath(&g, device.Path); err != nil {
return nil, err
}
}
}
g.ClearProcessEnv()
for name, val := range s.Env {
g.AddProcessEnv(name, val)

View File

@ -15,5 +15,17 @@ func setLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig s
}
func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *libimage.Image, rtc *config.Config) error {
// If this is a privileged container, change the devfs ruleset to expose all devices.
if s.Privileged {
for k, m := range g.Config.Mounts {
if m.Type == "devfs" {
m.Options = []string{
"ruleset=0",
}
g.Config.Mounts[k] = m
}
}
}
return nil
}