Fix handling of devices

Devices are supposed to be able to be passed in via the form of

--device /dev/foo
--device /dev/foo:/dev/bar
--device /dev/foo:rwm
--device /dev/foo:/dev/bar:rwm

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>

Closes: #1299
Approved by: umohnani8
This commit is contained in:
Daniel J Walsh
2018-08-17 18:17:44 -04:00
committed by Atomic Bot
parent e40c99a19e
commit 462c503a47
4 changed files with 89 additions and 85 deletions

View File

@ -38,19 +38,6 @@ type PortMapping struct {
HostIp string `protobuf:"bytes,4,opt,name=host_ip,json=hostIp,proto3" json:"host_ip,omitempty"`
}
// Device specifies a host device to mount into a container.
type HostDevice struct {
// Path of the device within the container.
ContainerPath string `protobuf:"bytes,1,opt,name=container_path,json=containerPath,proto3" json:"container_path,omitempty"`
// Path of the device on the host.
HostPath string `protobuf:"bytes,2,opt,name=host_path,json=hostPath,proto3" json:"host_path,omitempty"`
// Cgroups permissions of the device, candidates are one or more of
// * r - allows container to read from the specified device.
// * w - allows container to write to the specified device.
// * m - allows container to create device files that do not yet exist.
Permissions string `protobuf:"bytes,3,opt,name=permissions,proto3" json:"permissions,omitempty"`
}
// Note: for flags that are in the form <number><unit>, use the RAMInBytes function
// from the units package in docker/go-units/size.go
@ -106,75 +93,6 @@ func validateBlkioWeight(val int64) (int64, error) { //nolint
return -1, errors.Errorf("invalid blkio weight %q, should be between 10 and 1000", val)
}
// parseDevice parses a device mapping string to a container.DeviceMapping struct
// for device flag
func parseDevice(device string) (*HostDevice, error) { //nolint
_, err := validateDevice(device)
if err != nil {
return nil, errors.Wrapf(err, "device string not valid %q", device)
}
src := ""
dst := ""
permissions := "rwm"
arr := strings.Split(device, ":")
switch len(arr) {
case 3:
permissions = arr[2]
fallthrough
case 2:
if validDeviceMode(arr[1]) {
permissions = arr[1]
} else {
dst = arr[1]
}
fallthrough
case 1:
src = arr[0]
default:
return nil, fmt.Errorf("invalid device specification: %s", device)
}
if dst == "" {
dst = src
}
deviceMapping := &HostDevice{
ContainerPath: dst,
HostPath: src,
Permissions: permissions,
}
return deviceMapping, nil
}
// validDeviceMode checks if the mode for device is valid or not.
// Valid mode is a composition of r (read), w (write), and m (mknod).
func validDeviceMode(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
}
// validateDevice validates a path for devices
// It will make sure 'val' is in the form:
// [host-dir:]container-path[:mode]
// It also validates the device mode.
func validateDevice(val string) (string, error) {
return validatePath(val, validDeviceMode)
}
func validatePath(val string, validator func(string) bool) (string, error) {
var containerPath string
var mode string

View File

@ -28,10 +28,15 @@ func Device(d *configs.Device) spec.LinuxDevice {
}
func addDevice(g *generate.Generator, device string) error {
dev, err := devices.DeviceFromPath(device, "rwm")
src, dst, permissions, err := parseDevice(device)
if err != nil {
return errors.Wrapf(err, "%s is not a valid device", device)
return err
}
dev, err := devices.DeviceFromPath(src, permissions)
if err != nil {
return errors.Wrapf(err, "%s is not a valid device", src)
}
dev.Path = dst
linuxdev := spec.LinuxDevice{
Path: dev.Path,
Type: string(dev.Type),

View File

@ -126,3 +126,58 @@ func getLoggingPath(opts []string) string {
}
return ""
}
// parseDevice parses device mapping string to a src, dest & permissions string
func parseDevice(device string) (string, string, string, error) { //nolint
src := ""
dst := ""
permissions := "rwm"
arr := strings.Split(device, ":")
switch len(arr) {
case 3:
if !validDeviceMode(arr[2]) {
return "", "", "", fmt.Errorf("invalid device mode: %s", arr[2])
}
permissions = arr[2]
fallthrough
case 2:
if validDeviceMode(arr[1]) {
permissions = arr[1]
} else {
if arr[1][0] != '/' {
return "", "", "", fmt.Errorf("invalid device mode: %s", arr[2])
}
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
}
// validDeviceMode checks if the mode for device is valid or not.
// Valid mode is a composition of r (read), w (write), and m (mknod).
func validDeviceMode(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

@ -8,7 +8,7 @@ import (
. "github.com/onsi/gomega"
)
var _ = Describe("Podman kill", func() {
var _ = Describe("Podman run device", func() {
var (
tempdir string
err error
@ -43,4 +43,30 @@ var _ = Describe("Podman kill", func() {
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(Equal("/dev/kmsg"))
})
It("podman run device rename test", func() {
session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg:/dev/kmsg1", ALPINE, "ls", "--color=never", "/dev/kmsg1"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(Equal("/dev/kmsg1"))
})
It("podman run device permission test", func() {
session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg:r", ALPINE, "ls", "--color=never", "/dev/kmsg"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(Equal("/dev/kmsg"))
})
It("podman run device rename and permission test", func() {
session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg:/dev/kmsg1:r", ALPINE, "ls", "--color=never", "/dev/kmsg1"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(Equal("/dev/kmsg1"))
})
It("podman run device rename and bad permission test", func() {
session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg:/dev/kmsg1:rd", ALPINE, "ls", "--color=never", "/dev/kmsg1"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Not(Equal(0)))
})
})