mirror of
https://github.com/containers/podman.git
synced 2025-06-23 02:18:13 +08:00
Merge pull request #19483 from dfr/freebsd-devices
pkg/specgen: Add device support for FreeBSD
This commit is contained in:
64
pkg/specgen/generate/config_common.go
Normal file
64
pkg/specgen/generate/config_common.go
Normal 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
|
||||||
|
}
|
29
pkg/specgen/generate/config_common_test.go
Normal file
29
pkg/specgen/generate/config_common_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
118
pkg/specgen/generate/config_freebsd.go
Normal file
118
pkg/specgen/generate/config_freebsd.go
Normal 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")
|
||||||
|
}
|
@ -176,61 +176,6 @@ func addDevice(g *generate.Generator, device string) error {
|
|||||||
return nil
|
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 {
|
func supportAmbientCapabilities() bool {
|
||||||
err := unix.Prctl(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, 0, 0, 0)
|
err := unix.Prctl(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, 0, 0, 0)
|
||||||
return err == nil
|
return err == nil
|
||||||
|
@ -26,25 +26,3 @@ func TestShouldMask(t *testing.T) {
|
|||||||
assert.Equal(t, val, test.shouldMask)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -48,6 +48,28 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
|
|||||||
g.AddAnnotation(key, val)
|
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()
|
g.ClearProcessEnv()
|
||||||
for name, val := range s.Env {
|
for name, val := range s.Env {
|
||||||
g.AddProcessEnv(name, val)
|
g.AddProcessEnv(name, val)
|
||||||
|
@ -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 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user