Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add devices to containers #4191

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 57 additions & 0 deletions daemon/container.go
Expand Up @@ -415,9 +415,66 @@ func (container *Container) Start() (err error) {
}
container.waitLock = make(chan struct{})

if err := container.createDevices(); err != nil {
return err
}

return container.waitForStart()
}

func (container *Container) createDevices() error {
for _, device := range container.hostConfig.Devices {
src, dst, err := utils.ParseDevice(device)
if err != nil {
return err
}
dst = path.Join(container.RootfsPath(), dst)

stat, err := os.Stat(src)
if err != nil {
return err
}

devType := 'c'
mode := stat.Mode()
perm := os.FileMode.Perm(mode)
switch {
case (mode & os.ModeDevice) == 0:
return fmt.Errorf("%s is not a device", src)
case (mode & os.ModeCharDevice) != 0:
perm |= syscall.S_IFCHR
default:
perm |= syscall.S_IFBLK
devType = 'b'
}

devNumber := 0
sys, ok := stat.Sys().(*syscall.Stat_t)
if ok {
devNumber = int(sys.Rdev)
} else {
return fmt.Errorf("Cannot determine the device major and minor numbers")
}

oldMask := syscall.Umask(int(0))
if err := os.MkdirAll(path.Dir(dst), 0755); err != nil {
return err
}
if err := syscall.Mknod(dst, uint32(perm), devNumber); err != nil {
return err
}
syscall.Umask(oldMask)

devMajor := int64((devNumber >> 8) & 0xfff)
devMinor := int64((devNumber & 0xff) | ((devNumber >> 12) & 0xfff00))
if err := container.daemon.execDriver.AddDevice(container.command, devType, devMajor, devMinor); err != nil {
return err
}
}

return nil
}

func (container *Container) Run() error {
if err := container.Start(); err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions daemon/execdriver/driver.go
Expand Up @@ -85,6 +85,7 @@ type Driver interface {
Info(id string) Info // "temporary" hack (until we move state from core to plugins)
GetPidsForContainer(id string) ([]int, error) // Returns a list of pids for the given container.
Terminate(c *Command) error // kill it with fire
AddDevice(c *Command, devType rune, devMajor int64, devMinor int64) error
}

// Network settings of the container
Expand Down
7 changes: 7 additions & 0 deletions daemon/execdriver/lxc/driver.go
Expand Up @@ -81,6 +81,13 @@ func (d *driver) Name() string {
return fmt.Sprintf("%s-%s", DriverName, version)
}

func (d *driver) AddDevice(c *execdriver.Command, devType rune, devMajor int64, devMinor int64) error {
c.Config["lxc"] = append(c.Config["lxc"], fmt.Sprintf("cgroup.devices.allow = %c %d:%d rwm",
devType, devMajor, devMinor))

return nil
}

func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
if err := execdriver.SetTerminal(c, pipes); err != nil {
return -1, err
Expand Down
4 changes: 4 additions & 0 deletions daemon/execdriver/native/driver.go
Expand Up @@ -79,6 +79,10 @@ func NewDriver(root, initPath string) (*driver, error) {
}, nil
}

func (d *driver) AddDevice(c *execdriver.Command, devType rune, devMajor int64, devMinor int64) error {
return nil
}

func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
// take the Command and populate the libcontainer.Container from it
container, err := d.createContainer(c)
Expand Down
12 changes: 12 additions & 0 deletions docs/sources/reference/commandline/cli.rst
Expand Up @@ -1018,6 +1018,7 @@ image is removed.
-c, --cpu-shares=0: CPU shares (relative weight)
--cidfile="": Write the container ID to the file
-d, --detach=false: Detached mode: Run container in the background, print new container id
--device=[]: Add a host device to the container (e.g. --device=/dev/sdc[:/dev/xvdc])
-e, --env=[]: Set environment variables
--env-file="": Read in a line delimited file of ENV variables
-h, --hostname="": Container host name
Expand Down Expand Up @@ -1257,6 +1258,17 @@ could be retrieved using ``docker logs``. This is useful if you need to pipe
a file or something else into a container and retrieve the container's ID once
the container has finished running.

.. code-block:: bash

$ sudo docker run --device=/dev/sdc:/dev/xvdc --device=/dev/sdd --device=/dev/zero:/dev/nulo -i -t ubuntu ls -l /dev/{xvdc,sdd,nulo}
brw-rw---- 1 root disk 8, 2 Feb 9 16:05 /dev/xvdc
brw-rw---- 1 root disk 8, 3 Feb 9 16:05 /dev/sdd
crw-rw-rw- 1 root root 1, 5 Feb 9 16:05 /dev/nulo

It is often necessary to directly expose devices to a container. ``--device``
option enables that. For example, a specific block storage device or loop
device or audio device can be added to an otherwise 'unprivileged' container
(without ``-privileged`` flag) and have the application directly access it.

A complete example
..................
Expand Down
16 changes: 16 additions & 0 deletions integration-cli/docker_cli_run_test.go
Expand Up @@ -665,3 +665,19 @@ func TestUnPrivilegedCannotMount(t *testing.T) {

logDone("run - test un-privileged cannot mount")
}

func TestDevice(t *testing.T) {
cmd := exec.Command(dockerBinary, "run", "--device", "/dev/zero:/dev/nulo", "busybox", "sh", "-c", "ls /dev/nulo")

out, _, err := runCommandWithOutput(cmd)
if err != nil {
t.Fatal(err, out)
}

if actual := strings.Trim(out, "\r\n"); actual != "/dev/nulo" {
t.Fatalf("expected output /dev/nulo, received %s", actual)
}
deleteAllContainers()

logDone("run - test device")
}
1 change: 1 addition & 0 deletions runconfig/config.go
Expand Up @@ -65,5 +65,6 @@ func ContainerConfigFromJob(job *engine.Job) *Config {
if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil {
config.Entrypoint = Entrypoint
}

return config
}
4 changes: 4 additions & 0 deletions runconfig/hostconfig.go
Expand Up @@ -17,6 +17,7 @@ type HostConfig struct {
Dns []string
DnsSearch []string
VolumesFrom []string
Devices []string
}

func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
Expand All @@ -42,5 +43,8 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
if VolumesFrom := job.GetenvList("VolumesFrom"); VolumesFrom != nil {
hostConfig.VolumesFrom = VolumesFrom
}
if Devices := job.GetenvList("Devices"); Devices != nil {
hostConfig.Devices = Devices
}
return hostConfig
}
3 changes: 3 additions & 0 deletions runconfig/parse.go
Expand Up @@ -38,6 +38,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
flVolumes = opts.NewListOpts(opts.ValidatePath)
flLinks = opts.NewListOpts(opts.ValidateLink)
flEnv = opts.NewListOpts(opts.ValidateEnv)
flDevices = opts.NewListOpts(opts.ValidatePath)

flPublish opts.ListOpts
flExpose opts.ListOpts
Expand Down Expand Up @@ -70,6 +71,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to stdin, stdout or stderr.")
cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container (name:alias)")
cmd.Var(&flDevices, []string{"device", "-device"}, "Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc)")
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a line delimited file of ENV variables")

Expand Down Expand Up @@ -230,6 +232,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
Dns: flDns.GetAll(),
DnsSearch: flDnsSearch.GetAll(),
VolumesFrom: flVolumesFrom.GetAll(),
Devices: flDevices.GetAll(),
}

if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit {
Expand Down
21 changes: 21 additions & 0 deletions utils/utils.go
Expand Up @@ -1089,3 +1089,24 @@ func ParseKeyValueOpt(opt string) (string, string, error) {
}
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
}

func ParseDevice(device string) (string, string, error) {
src := ""
dst := ""
arr := strings.Split(device, ":")
switch len(arr) {
case 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, nil
}