diff --git a/api/swagger.yaml b/api/swagger.yaml index 4772399d30..54884ee810 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -1313,8 +1313,6 @@ definitions: - Network - Linux - Mounts - - Devices - - DeviceCreation - Env - Args properties: @@ -1365,23 +1363,23 @@ definitions: Linux: type: "object" x-nullable: false - required: [Capabilities] + required: [Capabilities, DeviceCreation, Devices] properties: Capabilities: type: "array" items: type: "string" + DeviceCreation: + type: "boolean" + x-nullable: false + Devices: + type: "array" + items: + $ref: "#/definitions/PluginDevice" Mounts: type: "array" items: $ref: "#/definitions/PluginMount" - Devices: - type: "array" - items: - $ref: "#/definitions/PluginDevice" - DeviceCreation: - type: "boolean" - x-nullable: false Env: type: "array" items: diff --git a/api/types/plugin.go b/api/types/plugin.go index 2c5c1d70b8..e8115e30f2 100644 --- a/api/types/plugin.go +++ b/api/types/plugin.go @@ -43,14 +43,6 @@ type PluginConfig struct { // Required: true Description string `json:"Description"` - // device creation - // Required: true - DeviceCreation bool `json:"DeviceCreation"` - - // devices - // Required: true - Devices []PluginDevice `json:"Devices"` - // documentation // Required: true Documentation string `json:"Documentation"` @@ -128,6 +120,14 @@ type PluginConfigLinux struct { // capabilities // Required: true Capabilities []string `json:"Capabilities"` + + // device creation + // Required: true + DeviceCreation bool `json:"DeviceCreation"` + + // devices + // Required: true + Devices []PluginDevice `json:"Devices"` } // PluginConfigNetwork plugin config network diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go index a55c688334..2296045765 100644 --- a/daemon/container_operations_unix.go +++ b/daemon/container_operations_unix.go @@ -8,13 +8,11 @@ import ( "os" "path/filepath" "strconv" - "strings" "syscall" "time" "github.com/Sirupsen/logrus" "github.com/cloudflare/cfssl/log" - containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/daemon/links" "github.com/docker/docker/pkg/idtools" @@ -22,16 +20,10 @@ import ( "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" "github.com/docker/libnetwork" - "github.com/opencontainers/runc/libcontainer/configs" - "github.com/opencontainers/runc/libcontainer/devices" "github.com/opencontainers/runc/libcontainer/label" - "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) -func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } -func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm } - func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) { var env []string children := daemon.children(container) @@ -247,78 +239,6 @@ func killProcessDirectly(container *container.Container) error { return nil } -func specDevice(d *configs.Device) specs.Device { - return specs.Device{ - Type: string(d.Type), - Path: d.Path, - Major: d.Major, - Minor: d.Minor, - FileMode: fmPtr(int64(d.FileMode)), - UID: u32Ptr(int64(d.Uid)), - GID: u32Ptr(int64(d.Gid)), - } -} - -func specDeviceCgroup(d *configs.Device) specs.DeviceCgroup { - t := string(d.Type) - return specs.DeviceCgroup{ - Allow: true, - Type: &t, - Major: &d.Major, - Minor: &d.Minor, - Access: &d.Permissions, - } -} - -func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []specs.Device, devPermissions []specs.DeviceCgroup, err error) { - resolvedPathOnHost := deviceMapping.PathOnHost - - // check if it is a symbolic link - if src, e := os.Lstat(deviceMapping.PathOnHost); e == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink { - if linkedPathOnHost, e := filepath.EvalSymlinks(deviceMapping.PathOnHost); e == nil { - resolvedPathOnHost = linkedPathOnHost - } - } - - device, err := devices.DeviceFromPath(resolvedPathOnHost, deviceMapping.CgroupPermissions) - // if there was no error, return the device - if err == nil { - device.Path = deviceMapping.PathInContainer - return append(devs, specDevice(device)), append(devPermissions, specDeviceCgroup(device)), nil - } - - // if the device is not a device node - // try to see if it's a directory holding many devices - if err == devices.ErrNotADevice { - - // check if it is a directory - if src, e := os.Stat(resolvedPathOnHost); e == nil && src.IsDir() { - - // mount the internal devices recursively - filepath.Walk(resolvedPathOnHost, func(dpath string, f os.FileInfo, e error) error { - childDevice, e := devices.DeviceFromPath(dpath, deviceMapping.CgroupPermissions) - if e != nil { - // ignore the device - return nil - } - - // add the device to userSpecified devices - childDevice.Path = strings.Replace(dpath, resolvedPathOnHost, deviceMapping.PathInContainer, 1) - devs = append(devs, specDevice(childDevice)) - devPermissions = append(devPermissions, specDeviceCgroup(childDevice)) - - return nil - }) - } - } - - if len(devs) > 0 { - return devs, devPermissions, nil - } - - return devs, devPermissions, fmt.Errorf("error gathering device information while adding custom device %q: %s", deviceMapping.PathOnHost, err) -} - func detachMounted(path string) error { return syscall.Unmount(path, syscall.MNT_DETACH) } diff --git a/daemon/oci_linux.go b/daemon/oci_linux.go index 5a2158c222..b3abd8c6cb 100644 --- a/daemon/oci_linux.go +++ b/daemon/oci_linux.go @@ -88,7 +88,7 @@ func setDevices(s *specs.Spec, c *container.Container) error { return err } for _, d := range hostDevices { - devs = append(devs, specDevice(d)) + devs = append(devs, oci.Device(d)) } rwm := "rwm" devPermissions = []specs.DeviceCgroup{ @@ -99,7 +99,7 @@ func setDevices(s *specs.Spec, c *container.Container) error { } } else { for _, deviceMapping := range c.HostConfig.Devices { - d, dPermissions, err := getDevicesFromPath(deviceMapping) + d, dPermissions, err := oci.DevicesFromPath(deviceMapping.PathOnHost, deviceMapping.PathInContainer, deviceMapping.CgroupPermissions) if err != nil { return err } diff --git a/oci/devices_linux.go b/oci/devices_linux.go new file mode 100644 index 0000000000..2840d2586a --- /dev/null +++ b/oci/devices_linux.go @@ -0,0 +1,86 @@ +package oci + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/runc/libcontainer/devices" + specs "github.com/opencontainers/runtime-spec/specs-go" +) + +// Device transforms a libcontainer configs.Device to a specs.Device object. +func Device(d *configs.Device) specs.Device { + return specs.Device{ + Type: string(d.Type), + Path: d.Path, + Major: d.Major, + Minor: d.Minor, + FileMode: fmPtr(int64(d.FileMode)), + UID: u32Ptr(int64(d.Uid)), + GID: u32Ptr(int64(d.Gid)), + } +} + +func deviceCgroup(d *configs.Device) specs.DeviceCgroup { + t := string(d.Type) + return specs.DeviceCgroup{ + Allow: true, + Type: &t, + Major: &d.Major, + Minor: &d.Minor, + Access: &d.Permissions, + } +} + +// DevicesFromPath computes a list of devices and device permissions from paths (pathOnHost and pathInContainer) and cgroup permissions. +func DevicesFromPath(pathOnHost, pathInContainer, cgroupPermissions string) (devs []specs.Device, devPermissions []specs.DeviceCgroup, err error) { + resolvedPathOnHost := pathOnHost + + // check if it is a symbolic link + if src, e := os.Lstat(pathOnHost); e == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink { + if linkedPathOnHost, e := filepath.EvalSymlinks(pathOnHost); e == nil { + resolvedPathOnHost = linkedPathOnHost + } + } + + device, err := devices.DeviceFromPath(resolvedPathOnHost, cgroupPermissions) + // if there was no error, return the device + if err == nil { + device.Path = pathInContainer + return append(devs, Device(device)), append(devPermissions, deviceCgroup(device)), nil + } + + // if the device is not a device node + // try to see if it's a directory holding many devices + if err == devices.ErrNotADevice { + + // check if it is a directory + if src, e := os.Stat(resolvedPathOnHost); e == nil && src.IsDir() { + + // mount the internal devices recursively + filepath.Walk(resolvedPathOnHost, func(dpath string, f os.FileInfo, e error) error { + childDevice, e := devices.DeviceFromPath(dpath, cgroupPermissions) + if e != nil { + // ignore the device + return nil + } + + // add the device to userSpecified devices + childDevice.Path = strings.Replace(dpath, resolvedPathOnHost, pathInContainer, 1) + devs = append(devs, Device(childDevice)) + devPermissions = append(devPermissions, deviceCgroup(childDevice)) + + return nil + }) + } + } + + if len(devs) > 0 { + return devs, devPermissions, nil + } + + return devs, devPermissions, fmt.Errorf("error gathering device information while adding custom device %q: %s", pathOnHost, err) +} diff --git a/oci/devices_unsupported.go b/oci/devices_unsupported.go new file mode 100644 index 0000000000..6252cab536 --- /dev/null +++ b/oci/devices_unsupported.go @@ -0,0 +1,20 @@ +// +build !linux + +package oci + +import ( + "errors" + + "github.com/opencontainers/runc/libcontainer/configs" + specs "github.com/opencontainers/runtime-spec/specs-go" +) + +// Device transforms a libcontainer configs.Device to a specs.Device object. +// Not implemented +func Device(d *configs.Device) specs.Device { return specs.Device{} } + +// DevicesFromPath computes a list of devices and device permissions from paths (pathOnHost and pathInContainer) and cgroup permissions. +// Not implemented +func DevicesFromPath(pathOnHost, pathInContainer, cgroupPermissions string) (devs []specs.Device, devPermissions []specs.DeviceCgroup, err error) { + return nil, nil, errors.New("oci/devices: unsupported platform") +} diff --git a/plugin/v2/plugin.go b/plugin/v2/plugin.go index c8aa4a82bc..ca1b5f1962 100644 --- a/plugin/v2/plugin.go +++ b/plugin/v2/plugin.go @@ -9,6 +9,7 @@ import ( "sync" "github.com/docker/docker/api/types" + "github.com/docker/docker/oci" "github.com/docker/docker/pkg/plugins" "github.com/docker/docker/pkg/system" specs "github.com/opencontainers/runtime-spec/specs-go" @@ -103,6 +104,8 @@ func (p *Plugin) InitPlugin() error { p.PluginObj.Settings.Mounts[i] = mount } p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env)) + p.PluginObj.Settings.Devices = make([]types.PluginDevice, 0, len(p.PluginObj.Config.Linux.Devices)) + copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices) for _, env := range p.PluginObj.Config.Env { if env.Value != nil { p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value)) @@ -175,7 +178,7 @@ next: } // range over all the devices in the config - for _, device := range p.PluginObj.Config.Devices { + for _, device := range p.PluginObj.Config.Linux.Devices { // found the device in the config if device.Name == s.name { // is it settable ? @@ -233,7 +236,7 @@ func (p *Plugin) ComputePrivileges() types.PluginPrivileges { }) } } - for _, device := range m.Devices { + for _, device := range m.Linux.Devices { if device.Path != nil { privileges = append(privileges, types.PluginPrivilege{ Name: "device", @@ -242,7 +245,7 @@ func (p *Plugin) ComputePrivileges() types.PluginPrivileges { }) } } - if m.DeviceCreation { + if m.Linux.DeviceCreation { privileges = append(privileges, types.PluginPrivilege{ Name: "device-creation", Description: "", @@ -299,12 +302,12 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) { Readonly: false, // TODO: all plugins should be readonly? settable in config? } - if p.PluginObj.Config.DeviceCreation { - rwm := "rwm" - s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}} + userMounts := make(map[string]struct{}, len(p.PluginObj.Config.Mounts)) + for _, m := range p.PluginObj.Config.Mounts { + userMounts[m.Destination] = struct{}{} } - mounts := append(p.PluginObj.Settings.Mounts, types.PluginMount{ + mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{ Source: &p.RuntimeSourcePath, Destination: defaultPluginRuntimeDestination, Type: "bind", @@ -363,11 +366,27 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) { } for i, m := range s.Mounts { - if strings.HasPrefix(m.Destination, "/dev/") && true { // TODO: && user specified /dev - s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...) + if strings.HasPrefix(m.Destination, "/dev/") { + if _, ok := userMounts[m.Destination]; ok { + s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...) + } } } + if p.PluginObj.Config.Linux.DeviceCreation { + rwm := "rwm" + s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}} + } + for _, dev := range p.PluginObj.Config.Linux.Devices { + path := *dev.Path + d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm") + if err != nil { + return nil, err + } + s.Linux.Devices = append(s.Linux.Devices, d...) + s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, dPermissions...) + } + envs := make([]string, 1, len(p.PluginObj.Settings.Env)+1) envs[0] = "PATH=" + system.DefaultPathEnv envs = append(envs, p.PluginObj.Settings.Env...)