mirror of
https://github.com/containers/podman.git
synced 2025-06-22 18:08:11 +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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user