瀏覽代碼

Merge branch 'master' into libcontainer-fixes

Conflicts:
	pkg/libcontainer/README.md
	pkg/libcontainer/container.json

Docker-DCO-1.1-Signed-off-by: Rohit Jnagal <jnagal@google.com> (github: rjnagal)
Rohit Jnagal 11 年之前
父節點
當前提交
c44c51e3ce
共有 60 個文件被更改,包括 1798 次插入781 次删除
  1. 6 3
      daemon/container.go
  2. 21 13
      daemon/execdriver/lxc/driver.go
  3. 10 1
      daemon/execdriver/lxc/lxc_template.go
  4. 15 1
      daemon/execdriver/native/create.go
  5. 8 1
      daemon/execdriver/native/driver.go
  6. 15 15
      daemon/volumes.go
  7. 51 57
      docs/README.md
  8. 2 0
      docs/mkdocs.yml
  9. 2 2
      docs/sources/index/accounts.md
  10. 25 4
      docs/sources/installation/ubuntulinux.md
  11. 2 2
      docs/sources/introduction/understanding-docker.md
  12. 1 0
      docs/sources/reference/api/archive/docker_remote_api_v1.7.rst
  13. 1 0
      docs/sources/reference/api/archive/docker_remote_api_v1.8.rst
  14. 1 72
      docs/sources/reference/api/docker_io_accounts_api.md
  15. 77 77
      docs/sources/reference/api/docker_remote_api.md
  16. 5 0
      docs/sources/reference/api/docker_remote_api_v1.10.md
  17. 1 0
      docs/sources/reference/api/docker_remote_api_v1.10.rst
  18. 5 0
      docs/sources/reference/api/docker_remote_api_v1.11.md
  19. 1 0
      docs/sources/reference/api/docker_remote_api_v1.11.rst
  20. 5 0
      docs/sources/reference/api/docker_remote_api_v1.9.md
  21. 1 0
      docs/sources/reference/api/docker_remote_api_v1.9.rst
  22. 12 3
      engine/engine.go
  23. 7 0
      engine/env.go
  24. 28 0
      engine/env_test.go
  25. 4 0
      engine/job.go
  26. 120 0
      engine/remote.go
  27. 3 0
      engine/remote_test.go
  28. 43 0
      engine/rengine/main.go
  29. 119 0
      engine/spawn/spawn.go
  30. 61 0
      engine/spawn/subengine/main.go
  31. 22 0
      integration-cli/docker_cli_run_test.go
  32. 4 0
      integration/MAINTAINERS
  33. 23 0
      integration/README.md
  34. 27 1
      integration/container_test.go
  35. 169 0
      pkg/cgroups/fs/blkio_test.go
  36. 19 3
      pkg/cgroups/fs/memory.go
  37. 75 0
      pkg/cgroups/fs/test_util.go
  38. 11 0
      pkg/cgroups/fs/utils.go
  39. 68 0
      pkg/cgroups/fs/utils_test.go
  40. 88 75
      pkg/libcontainer/README.md
  41. 60 0
      pkg/libcontainer/console/console.go
  42. 1 10
      pkg/libcontainer/container.go
  43. 144 127
      pkg/libcontainer/container.json
  44. 0 10
      pkg/libcontainer/container_test.go
  45. 143 0
      pkg/libcontainer/mount/init.go
  46. 19 0
      pkg/libcontainer/mount/msmoveroot.go
  47. 49 0
      pkg/libcontainer/mount/nodes/nodes.go
  48. 31 0
      pkg/libcontainer/mount/pivotroot.go
  49. 26 0
      pkg/libcontainer/mount/ptmx.go
  50. 12 0
      pkg/libcontainer/mount/readonly.go
  51. 31 0
      pkg/libcontainer/mount/remount.go
  52. 3 2
      pkg/libcontainer/nsinit/execin.go
  53. 10 27
      pkg/libcontainer/nsinit/init.go
  54. 0 265
      pkg/libcontainer/nsinit/mount.go
  55. 0 0
      pkg/libcontainer/security/capabilities/capabilities.go
  56. 51 0
      pkg/libcontainer/security/restrict/restrict.go
  57. 20 0
      pkg/libcontainer/types.go
  58. 25 8
      registry/registry.go
  59. 1 1
      registry/registry_mock_test.go
  60. 14 1
      registry/registry_test.go

+ 6 - 3
daemon/container.go

@@ -654,9 +654,12 @@ func (container *Container) Kill() error {
 
 	// 2. Wait for the process to die, in last resort, try to kill the process directly
 	if err := container.WaitTimeout(10 * time.Second); err != nil {
-		log.Printf("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", utils.TruncateID(container.ID))
-		if err := syscall.Kill(container.State.Pid, 9); err != nil {
-			return err
+		// Ensure that we don't kill ourselves
+		if pid := container.State.Pid; pid != 0 {
+			log.Printf("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", utils.TruncateID(container.ID))
+			if err := syscall.Kill(pid, 9); err != nil {
+				return err
+			}
 		}
 	}
 

+ 21 - 13
daemon/execdriver/lxc/driver.go

@@ -59,9 +59,10 @@ func init() {
 }
 
 type driver struct {
-	root       string // root path for the driver to use
-	apparmor   bool
-	sharedRoot bool
+	root            string // root path for the driver to use
+	apparmor        bool
+	sharedRoot      bool
+	restrictionPath string
 }
 
 func NewDriver(root string, apparmor bool) (*driver, error) {
@@ -69,10 +70,15 @@ func NewDriver(root string, apparmor bool) (*driver, error) {
 	if err := linkLxcStart(root); err != nil {
 		return nil, err
 	}
+	restrictionPath := filepath.Join(root, "empty")
+	if err := os.MkdirAll(restrictionPath, 0700); err != nil {
+		return nil, err
+	}
 	return &driver{
-		apparmor:   apparmor,
-		root:       root,
-		sharedRoot: rootIsShared(),
+		apparmor:        apparmor,
+		root:            root,
+		sharedRoot:      rootIsShared(),
+		restrictionPath: restrictionPath,
 	}, nil
 }
 
@@ -403,14 +409,16 @@ func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) {
 
 	if err := LxcTemplateCompiled.Execute(fo, struct {
 		*execdriver.Command
-		AppArmor     bool
-		ProcessLabel string
-		MountLabel   string
+		AppArmor          bool
+		ProcessLabel      string
+		MountLabel        string
+		RestrictionSource string
 	}{
-		Command:      c,
-		AppArmor:     d.apparmor,
-		ProcessLabel: process,
-		MountLabel:   mount,
+		Command:           c,
+		AppArmor:          d.apparmor,
+		ProcessLabel:      process,
+		MountLabel:        mount,
+		RestrictionSource: d.restrictionPath,
 	}); err != nil {
 		return "", err
 	}

+ 10 - 1
daemon/execdriver/lxc/lxc_template.go

@@ -88,7 +88,9 @@ lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noex
 
 # WARNING: sysfs is a known attack vector and should probably be disabled
 # if your userspace allows it. eg. see http://bit.ly/T9CkqJ
+{{if .Privileged}}
 lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0
+{{end}}
 
 {{if .Tty}}
 lxc.mount.entry = {{.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bind,rw 0 0
@@ -109,8 +111,15 @@ lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabS
 {{if .AppArmor}}
 lxc.aa_profile = unconfined
 {{else}}
-#lxc.aa_profile = unconfined
+# not unconfined
 {{end}}
+{{else}}
+# restrict access to proc
+lxc.mount.entry = {{.RestrictionSource}} {{escapeFstabSpaces $ROOTFS}}/proc/sys none bind,ro 0 0
+lxc.mount.entry = {{.RestrictionSource}} {{escapeFstabSpaces $ROOTFS}}/proc/irq none bind,ro 0 0
+lxc.mount.entry = {{.RestrictionSource}} {{escapeFstabSpaces $ROOTFS}}/proc/acpi none bind,ro 0 0
+lxc.mount.entry = {{escapeFstabSpaces $ROOTFS}}/dev/null {{escapeFstabSpaces $ROOTFS}}/proc/sysrq-trigger none bind,ro 0 0
+lxc.mount.entry = {{escapeFstabSpaces $ROOTFS}}/dev/null {{escapeFstabSpaces $ROOTFS}}/proc/kcore none bind,ro 0 0
 {{end}}
 
 # limits

+ 15 - 1
daemon/execdriver/native/create.go

@@ -25,6 +25,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container
 	container.Cgroups.Name = c.ID
 	// check to see if we are running in ramdisk to disable pivot root
 	container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
+	container.Context["restriction_path"] = d.restrictionPath
 
 	if err := d.createNetwork(container, c); err != nil {
 		return nil, err
@@ -33,6 +34,8 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container
 		if err := d.setPrivileged(container); err != nil {
 			return nil, err
 		}
+	} else {
+		container.Mounts = append(container.Mounts, libcontainer.Mount{Type: "devtmpfs"})
 	}
 	if err := d.setupCgroups(container, c); err != nil {
 		return nil, err
@@ -81,6 +84,11 @@ func (d *driver) setPrivileged(container *libcontainer.Container) error {
 		c.Enabled = true
 	}
 	container.Cgroups.DeviceAccess = true
+
+	// add sysfs as a mount for privileged containers
+	container.Mounts = append(container.Mounts, libcontainer.Mount{Type: "sysfs"})
+	delete(container.Context, "restriction_path")
+
 	if apparmor.IsEnabled() {
 		container.Context["apparmor_profile"] = "unconfined"
 	}
@@ -99,7 +107,13 @@ func (d *driver) setupCgroups(container *libcontainer.Container, c *execdriver.C
 
 func (d *driver) setupMounts(container *libcontainer.Container, c *execdriver.Command) error {
 	for _, m := range c.Mounts {
-		container.Mounts = append(container.Mounts, libcontainer.Mount{m.Source, m.Destination, m.Writable, m.Private})
+		container.Mounts = append(container.Mounts, libcontainer.Mount{
+			Type:        "bind",
+			Source:      m.Source,
+			Destination: m.Destination,
+			Writable:    m.Writable,
+			Private:     m.Private,
+		})
 	}
 	return nil
 }

+ 8 - 1
daemon/execdriver/native/driver.go

@@ -23,7 +23,7 @@ import (
 
 const (
 	DriverName                = "native"
-	Version                   = "0.1"
+	Version                   = "0.2"
 	BackupApparmorProfilePath = "apparmor/docker.back" // relative to docker root
 )
 
@@ -62,6 +62,7 @@ type driver struct {
 	root             string
 	initPath         string
 	activeContainers map[string]*exec.Cmd
+	restrictionPath  string
 }
 
 func NewDriver(root, initPath string) (*driver, error) {
@@ -72,8 +73,14 @@ func NewDriver(root, initPath string) (*driver, error) {
 	if err := apparmor.InstallDefaultProfile(filepath.Join(root, "../..", BackupApparmorProfilePath)); err != nil {
 		return nil, err
 	}
+	restrictionPath := filepath.Join(root, "empty")
+	if err := os.MkdirAll(restrictionPath, 0700); err != nil {
+		return nil, err
+	}
+
 	return &driver{
 		root:             root,
+		restrictionPath:  restrictionPath,
 		initPath:         initPath,
 		activeContainers: make(map[string]*exec.Cmd),
 	}, nil

+ 15 - 15
daemon/volumes.go

@@ -246,22 +246,22 @@ func createVolumes(container *Container) error {
 					if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil {
 						return err
 					}
+				}
+			}
 
-					var stat syscall.Stat_t
-					if err := syscall.Stat(rootVolPath, &stat); err != nil {
-						return err
-					}
-					var srcStat syscall.Stat_t
-					if err := syscall.Stat(srcPath, &srcStat); err != nil {
-						return err
-					}
-					// Change the source volume's ownership if it differs from the root
-					// files that were just copied
-					if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid {
-						if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil {
-							return err
-						}
-					}
+			var stat syscall.Stat_t
+			if err := syscall.Stat(rootVolPath, &stat); err != nil {
+				return err
+			}
+			var srcStat syscall.Stat_t
+			if err := syscall.Stat(srcPath, &srcStat); err != nil {
+				return err
+			}
+			// Change the source volume's ownership if it differs from the root
+			// files that were just copied
+			if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid {
+				if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil {
+					return err
 				}
 			}
 		}

+ 51 - 57
docs/README.md

@@ -4,74 +4,70 @@ Docker Documentation
 Overview
 --------
 
-The source for Docker documentation is here under ``sources/`` and uses
+The source for Docker documentation is here under `sources/` and uses
 extended Markdown, as implemented by [mkdocs](http://mkdocs.org).
 
-The HTML files are built and hosted on https://docs.docker.io, and update
-automatically after each change to the master or release branch of the
-[docker files on GitHub](https://github.com/dotcloud/docker) thanks to
-post-commit hooks. The "release" branch maps to the "latest"
-documentation and the "master" (unreleased development) branch maps to the "master"
-documentation. 
+The HTML files are built and hosted on `https://docs.docker.io`, and
+update automatically after each change to the master or release branch
+of [Docker on GitHub](https://github.com/dotcloud/docker)
+thanks to post-commit hooks. The "docs" branch maps to the "latest"
+documentation and the "master" (unreleased development) branch maps to
+the "master" documentation.
 
 ## Branches
 
-**There are two branches related to editing docs**: ``master`` and a
-``docs`` branch. You should always edit
-docs on a local branch of the ``master`` branch, and send a PR against ``master``. 
-That way your fixes 
-will automatically get included in later releases, and docs maintainers 
-can easily cherry-pick your changes into the ``docs`` release branch. 
-In the rare case where your change is not forward-compatible, 
-you may need to base your changes on the ``docs`` branch.
+**There are two branches related to editing docs**: `master` and a
+`docs` branch. You should always edit documentation on a local branch
+of the `master` branch, and send a PR against `master`.
 
-Now that we have a ``docs`` branch, we can keep the [http://docs.docker.io](http://docs.docker.io) docs
-up to date with any bugs found between ``docker`` code releases.
+That way your fixes will automatically get included in later releases,
+and docs maintainers can easily cherry-pick your changes into the
+`docs` release branch. In the rare case where your change is not
+forward-compatible, you may need to base your changes on the `docs`
+branch.
 
-**Warning**: When *reading* the docs, the [http://beta-docs.docker.io](http://beta-docs.docker.io) documentation may
-include features not yet part of any official docker
-release. The ``beta-docs`` site should be used only for understanding
-bleeding-edge development and ``docs.docker.io`` (which points to the ``docs``
-branch``) should be used for the latest official release.
+Also, now that we have a `docs` branch, we can keep the
+[http://docs.docker.io](http://docs.docker.io) docs up to date with any
+bugs found between `docker` code releases.
+
+**Warning**: When *reading* the docs, the
+[http://beta-docs.docker.io](http://beta-docs.docker.io) documentation
+may include features not yet part of any official docker release. The
+`beta-docs` site should be used only for understanding bleeding-edge
+development and `docs.docker.io` (which points to the `docs`
+branch`) should be used for the latest official release.
 
 Getting Started
 ---------------
 
-Docker documentation builds are done in a docker container, which installs all
-the required tools, adds the local ``docs/`` directory and builds the HTML
-docs. It then starts a HTTP server on port 8000 so that you can connect 
-and see your changes.
+Docker documentation builds are done in a Docker container, which
+installs all the required tools, adds the local `docs/` directory and
+builds the HTML docs. It then starts a HTTP server on port 8000 so that
+you can connect and see your changes.
 
-In the ``docker`` source directory, run:
-    ```make docs```
+In the root of the `docker` source directory:
 
-If you have any issues you need to debug, you can use ``make docs-shell`` and
-then run ``mkdocs serve``
+    cd docker
 
-# Contributing
+Run:
+
+    make docs
 
-## Normal Case:
+If you have any issues you need to debug, you can use `make docs-shell` and
+then run `mkdocs serve`
+
+# Contributing
 
 * Follow the contribution guidelines ([see
-  ``../CONTRIBUTING.md``](../CONTRIBUTING.md)).
+  `../CONTRIBUTING.md`](../CONTRIBUTING.md)).
 * [Remember to sign your work!](../CONTRIBUTING.md#sign-your-work)
-* Work in your own fork of the code, we accept pull requests.
-* Change the ``.md`` files with your favorite editor -- try to keep the
-  lines short (80 chars) and respect Markdown conventions. 
-* Run ``make clean docs`` to clean up old files and generate new ones,
-  or just ``make docs`` to update after small changes.
-* Your static website can now be found in the ``_build`` directory.
-* To preview what you have generated run ``make server`` and open
-  http://localhost:8000/ in your favorite browser.
-
-``make clean docs`` must complete without any warnings or errors.
 
 Working using GitHub's file editor
 ----------------------------------
 
 Alternatively, for small changes and typos you might want to use
 GitHub's built in file editor. It allows you to preview your changes
-right online (though there can be some differences between GitHub
+right on-line (though there can be some differences between GitHub
 Markdown and mkdocs Markdown). Just be careful not to create many commits.
 And you must still [sign your work!](../CONTRIBUTING.md#sign-your-work)
 
@@ -79,26 +75,24 @@ Images
 ------
 
 When you need to add images, try to make them as small as possible
-(e.g. as gif). Usually images should go in the same directory as the
-.md file which references them, or in a subdirectory if one already
+(e.g. as gifs). Usually images should go in the same directory as the
+`.md` file which references them, or in a subdirectory if one already
 exists.
 
 Publishing Documentation
 ------------------------
 
-To publish a copy of the documentation you need a ``docs/awsconfig``
-file containing AWS settings to deploy to. The release script will 
+To publish a copy of the documentation you need a `docs/awsconfig`
+file containing AWS settings to deploy to. The release script will
 create an s3 if needed, and will then push the files to it.
 
-```
-[profile dowideit-docs]
-aws_access_key_id = IHOIUAHSIDH234rwf....
-aws_secret_access_key = OIUYSADJHLKUHQWIUHE......
-region = ap-southeast-2
-```
+    [profile dowideit-docs]
+    aws_access_key_id = IHOIUAHSIDH234rwf....
+    aws_secret_access_key = OIUYSADJHLKUHQWIUHE......
+    region = ap-southeast-2
 
-The ``profile`` name must be the same as the name of the bucket you are 
-deploying to - which you call from the docker directory:
+The `profile` name must be the same as the name of the bucket you are
+deploying to - which you call from the `docker` directory:
 
-``make AWS_S3_BUCKET=dowideit-docs docs-release``
+    make AWS_S3_BUCKET=dowideit-docs docs-release
 

+ 2 - 0
docs/mkdocs.yml

@@ -106,6 +106,8 @@ pages:
 - ['reference/api/docker_remote_api_v1.10.md', 'Reference', 'Docker Remote API v1.10']
 - ['reference/api/docker_remote_api_v1.9.md', 'Reference', 'Docker Remote API v1.9']
 - ['reference/api/remote_api_client_libraries.md', 'Reference', 'Docker Remote API Client Libraries']
+- ['reference/api/docker_io_oauth_api.md', 'Reference', 'Docker IO OAuth API']
+- ['reference/api/docker_io_accounts_api.md', 'Reference', 'Docker IO Accounts API']
 
 # Contribute:
 - ['contributing/index.md', '**HIDDEN**']

+ 2 - 2
docs/sources/index/accounts.md

@@ -14,7 +14,7 @@ to need a [Docker IO](https://www.docker.io) account.
 ### Registration for a Docker IO Account
 
 You can get a Docker IO account by [signing up for one here](
-https://index.docker.io/account/signup/). A valid email address is required to
+https://www.docker.io/account/signup/). A valid email address is required to
 register, which you will need to verify for account activation.
 
 ### Email activation process
@@ -22,7 +22,7 @@ register, which you will need to verify for account activation.
 You need to have at least one verified email address to be able to use your
 Docker IO account. If you can't find the validation email, you can request
 another by visiting the [Resend Email Confirmation](
-https://index.docker.io/account/resend-email-confirmation/) page.
+https://www.docker.io/account/resend-email-confirmation/) page.
 
 ### Password reset process
 

+ 25 - 4
docs/sources/installation/ubuntulinux.md

@@ -4,10 +4,6 @@ page_keywords: Docker, Docker documentation, requirements, virtualbox, vagrant,
 
 # Ubuntu
 
-> **Warning**: 
-> These instructions have changed for 0.6. If you are upgrading from an
-> earlier version, you will need to follow them again.
-
 > **Note**:
 > Docker is still under heavy development! We don't recommend using it in
 > production yet, but we're getting closer with each release. Please see
@@ -16,6 +12,7 @@ page_keywords: Docker, Docker documentation, requirements, virtualbox, vagrant,
 
 Docker is supported on the following versions of Ubuntu:
 
+ - [*Ubuntu Trusty 14.04 (LTS) (64-bit)*](#ubuntu-trusty-1404-lts-64-bit)
  - [*Ubuntu Precise 12.04 (LTS) (64-bit)*](#ubuntu-precise-1204-lts-64-bit)
  - [*Ubuntu Raring 13.04 and Saucy 13.10 (64
    bit)*](#ubuntu-raring-1304-and-saucy-1310-64-bit)
@@ -23,6 +20,30 @@ Docker is supported on the following versions of Ubuntu:
 Please read [*Docker and UFW*](#docker-and-ufw), if you plan to use [UFW
 (Uncomplicated Firewall)](https://help.ubuntu.com/community/UFW)
 
+## Ubuntu Trusty 14.04 (LTS) (64-bit)
+
+Ubuntu Trusty comes with a 3.13.0 Linux kernel, and a `docker.io` package which
+installs all its prerequisites from Ubuntu's repository.
+
+> **Note**:
+> Ubuntu (and Debian) contain a much older KDE3/GNOME2 package called ``docker``, so the
+> package and the executable are called ``docker.io``.
+
+### Installation
+
+To install the latest Ubuntu package (may not be the latest Docker release):
+
+    sudo apt-get update
+    sudo apt-get install docker.io
+    sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker
+
+To verify that everything has worked as expected:
+
+    sudo docker run -i -t ubuntu /bin/bash
+
+Which should download the `ubuntu` image, and then start `bash` in a container.
+
+
 ## Ubuntu Precise 12.04 (LTS) (64-bit)
 
 This installation path should work at all times.

+ 2 - 2
docs/sources/introduction/understanding-docker.md

@@ -87,14 +87,14 @@ to you.
 *Docker is made for humans.*
 
 It's easy to get started and easy to build and deploy applications with
-Docker: or as we say "*dockerise*" them! As much of Docker as possible
+Docker: or as we say "*dockerize*" them! As much of Docker as possible
 uses plain English for commands and tries to be as lightweight and
 transparent as possible. We want to get out of the way so you can build
 and deploy your applications.
 
 ### Docker is Portable
 
-*Dockerise And Go!*
+*Dockerize And Go!*
 
 Docker containers are highly portable. Docker provides a standard
 container format to hold your applications:

+ 1 - 0
docs/sources/reference/api/archive/docker_remote_api_v1.7.rst

@@ -454,6 +454,7 @@ Kill a container
 
 	   HTTP/1.1 204 OK
 	   	
+	:query signal: Signal to send to the container (integer). When not set, SIGKILL is assumed and the call will waits for the container to exit.
 	:statuscode 204: no error
 	:statuscode 404: no such container
 	:statuscode 500: server error

+ 1 - 0
docs/sources/reference/api/archive/docker_remote_api_v1.8.rst

@@ -482,6 +482,7 @@ Kill a container
 
            HTTP/1.1 204 OK
 
+        :query signal: Signal to send to the container (integer). When not set, SIGKILL is assumed and the call will waits for the container to exit.
         :statuscode 204: no error
         :statuscode 404: no such container
         :statuscode 500: server error

+ 1 - 72
docs/sources/reference/api/docker_io_accounts_api.md

@@ -237,78 +237,7 @@ automatically sent.
             "primary": false
         }
 
-### 1.5 Update an email address for a user
-
-`PATCH /api/v1.1/users/:username/emails/`
-
-Update an email address for the specified user to either verify an
-email address or set it as the primary email for the user. You
-cannot use this endpoint to un-verify an email address. You cannot
-use this endpoint to unset the primary email, only set another as
-the primary.
-
-    Parameters:
-
-    -   **username** – username of the user whose email info is being
-        updated.
-
-    Json Parameters:
-
-     
-
-    -   **email** (*string*) – the email address to be updated.
-    -   **verified** (*boolean*) – (optional) whether the email address
-        is verified, must be `true` or absent.
-    -   **primary** (*boolean*) – (optional) whether to set the email
-        address as the primary email, must be `true`
-        or absent.
-
-    Request Headers:
-
-     
-
-    -   **Authorization** – required authentication credentials of
-        either type HTTP Basic or OAuth Bearer Token.
-    -   **Content-Type** – MIME Type of post data. JSON, url-encoded
-        form data, etc.
-
-    Status Codes:
-
-    -   **200** – success, user's email updated.
-    -   **400** – data validation error.
-    -   **401** – authentication error.
-    -   **403** – permission error, authenticated user must be the user
-        whose data is being updated, OAuth access tokens must have
-        `email_write` scope.
-    -   **404** – the specified username or email address does not
-        exist.
-
-    **Example request**:
-
-    Once you have independently verified an email address.
-
-        PATCH /api/v1.1/users/janedoe/emails/ HTTP/1.1
-        Host: www.docker.io
-        Accept: application/json
-        Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
-
-        {
-            "email": "jane.doe+other@example.com",
-            "verified": true,
-        }
-
-    **Example response**:
-
-        HTTP/1.1 200 OK
-        Content-Type: application/json
-
-        {
-            "email": "jane.doe+other@example.com",
-            "verified": true,
-            "primary": false
-        }
-
-### 1.6 Delete email address for a user
+### 1.5 Delete email address for a user
 
 `DELETE /api/v1.1/users/:username/emails/`
 

+ 77 - 77
docs/sources/reference/api/docker_remote_api.md

@@ -127,83 +127,83 @@ entry for each repo/tag on an image, each image is only represented
 once, with a nested attribute indicating the repo/tags that apply to
 that image.
 
-    Instead of:
-
-        HTTP/1.1 200 OK
-        Content-Type: application/json
-
-        [
-          {
-            "VirtualSize": 131506275,
-            "Size": 131506275,
-            "Created": 1365714795,
-            "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
-            "Tag": "12.04",
-            "Repository": "ubuntu"
-          },
-          {
-            "VirtualSize": 131506275,
-            "Size": 131506275,
-            "Created": 1365714795,
-            "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
-            "Tag": "latest",
-            "Repository": "ubuntu"
-          },
-          {
-            "VirtualSize": 131506275,
-            "Size": 131506275,
-            "Created": 1365714795,
-            "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
-            "Tag": "precise",
-            "Repository": "ubuntu"
-          },
-          {
-            "VirtualSize": 180116135,
-            "Size": 24653,
-            "Created": 1364102658,
-            "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
-            "Tag": "12.10",
-            "Repository": "ubuntu"
-          },
-          {
-            "VirtualSize": 180116135,
-            "Size": 24653,
-            "Created": 1364102658,
-            "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
-            "Tag": "quantal",
-            "Repository": "ubuntu"
-          }
-        ]
-
-    The returned json looks like this:
-
-        HTTP/1.1 200 OK
-        Content-Type: application/json
-
-        [
-          {
-             "RepoTags": [
-               "ubuntu:12.04",
-               "ubuntu:precise",
-               "ubuntu:latest"
-             ],
-             "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
-             "Created": 1365714795,
-             "Size": 131506275,
-             "VirtualSize": 131506275
-          },
-          {
-             "RepoTags": [
-               "ubuntu:12.10",
-               "ubuntu:quantal"
-             ],
-             "ParentId": "27cf784147099545",
-             "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
-             "Created": 1364102658,
-             "Size": 24653,
-             "VirtualSize": 180116135
-          }
-        ]
+Instead of:
+
+    HTTP/1.1 200 OK
+    Content-Type: application/json
+
+    [
+      {
+        "VirtualSize": 131506275,
+        "Size": 131506275,
+        "Created": 1365714795,
+        "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
+        "Tag": "12.04",
+        "Repository": "ubuntu"
+      },
+      {
+        "VirtualSize": 131506275,
+        "Size": 131506275,
+        "Created": 1365714795,
+        "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
+        "Tag": "latest",
+        "Repository": "ubuntu"
+      },
+      {
+        "VirtualSize": 131506275,
+        "Size": 131506275,
+        "Created": 1365714795,
+        "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
+        "Tag": "precise",
+        "Repository": "ubuntu"
+      },
+      {
+        "VirtualSize": 180116135,
+        "Size": 24653,
+        "Created": 1364102658,
+        "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+        "Tag": "12.10",
+        "Repository": "ubuntu"
+      },
+      {
+        "VirtualSize": 180116135,
+        "Size": 24653,
+        "Created": 1364102658,
+        "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+        "Tag": "quantal",
+        "Repository": "ubuntu"
+      }
+    ]
+
+The returned json looks like this:
+
+    HTTP/1.1 200 OK
+    Content-Type: application/json
+
+    [
+      {
+         "RepoTags": [
+           "ubuntu:12.04",
+           "ubuntu:precise",
+           "ubuntu:latest"
+         ],
+         "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
+         "Created": 1365714795,
+         "Size": 131506275,
+         "VirtualSize": 131506275
+      },
+      {
+         "RepoTags": [
+           "ubuntu:12.10",
+           "ubuntu:quantal"
+         ],
+         "ParentId": "27cf784147099545",
+         "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+         "Created": 1364102658,
+         "Size": 24653,
+         "VirtualSize": 180116135
+      }
+    ]
 
 `GET /images/viz`
 

+ 5 - 0
docs/sources/reference/api/docker_remote_api_v1.10.md

@@ -459,6 +459,11 @@ Kill the container `id`
 
         HTTP/1.1 204 OK
 
+    Query Parameters
+
+    -   **signal** - Signal to send to the container: integer or string like "SIGINT".
+        When not set, SIGKILL is assumed and the call will waits for the container to exit.
+
     Status Codes:
 
     -   **204** – no error

+ 1 - 0
docs/sources/reference/api/docker_remote_api_v1.10.rst

@@ -466,6 +466,7 @@ Kill a container
 
            HTTP/1.1 204 OK
 
+        :query signal: Signal to send to the container, must be integer or string (i.e. SIGINT). When not set, SIGKILL is assumed and the call will waits for the container to exit.
         :statuscode 204: no error
         :statuscode 404: no such container
         :statuscode 500: server error

+ 5 - 0
docs/sources/reference/api/docker_remote_api_v1.11.md

@@ -461,6 +461,11 @@ Kill the container `id`
 
         HTTP/1.1 204 OK
 
+    Query Parameters
+
+    -   **signal** - Signal to send to the container: integer or string like "SIGINT".
+        When not set, SIGKILL is assumed and the call will waits for the container to exit.
+
     Status Codes:
 
     -   **204** – no error

+ 1 - 0
docs/sources/reference/api/docker_remote_api_v1.11.rst

@@ -468,6 +468,7 @@ Kill a container
 
            HTTP/1.1 204 OK
 
+        :query signal: Signal to send to the container, must be integer or string (i.e. SIGINT). When not set, SIGKILL is assumed and the call will waits for the container to exit.
         :statuscode 204: no error
         :statuscode 404: no such container
         :statuscode 500: server error

+ 5 - 0
docs/sources/reference/api/docker_remote_api_v1.9.md

@@ -482,6 +482,11 @@ Kill the container `id`
 
         HTTP/1.1 204 OK
 
+    Query Parameters
+
+    -   **signal** - Signal to send to the container: integer or string like "SIGINT".
+        When not set, SIGKILL is assumed and the call will waits for the container to exit.
+
     Status Codes:
 
     -   **204** – no error

+ 1 - 0
docs/sources/reference/api/docker_remote_api_v1.9.rst

@@ -480,6 +480,7 @@ Kill a container
 
            HTTP/1.1 204 OK
 
+        :query signal: Signal to send to the container, must be integer or string (i.e. SIGINT). When not set, SIGKILL is assumed and the call will waits for the container to exit.
         :statuscode 204: no error
         :statuscode 404: no such container
         :statuscode 500: server error

+ 12 - 3
engine/engine.go

@@ -43,6 +43,7 @@ func unregister(name string) {
 // containers by executing *jobs*.
 type Engine struct {
 	handlers map[string]Handler
+	catchall Handler
 	hack     Hack // data for temporary hackery (see hack.go)
 	id       string
 	Stdout   io.Writer
@@ -60,6 +61,10 @@ func (eng *Engine) Register(name string, handler Handler) error {
 	return nil
 }
 
+func (eng *Engine) RegisterCatchall(catchall Handler) {
+	eng.catchall = catchall
+}
+
 // New initializes a new engine.
 func New() *Engine {
 	eng := &Engine{
@@ -113,9 +118,13 @@ func (eng *Engine) Job(name string, args ...string) *Job {
 	if eng.Logging {
 		job.Stderr.Add(utils.NopWriteCloser(eng.Stderr))
 	}
-	handler, exists := eng.handlers[name]
-	if exists {
-		job.handler = handler
+	if eng.catchall != nil {
+		job.handler = eng.catchall
+	} else {
+		handler, exists := eng.handlers[name]
+		if exists {
+			job.handler = handler
+		}
 	}
 	return job
 }

+ 7 - 0
engine/env.go

@@ -36,6 +36,13 @@ func (env *Env) Exists(key string) bool {
 	return exists
 }
 
+// Len returns the number of keys in the environment.
+// Note that len(env) might be different from env.Len(),
+// because the same key might be set multiple times.
+func (env *Env) Len() int {
+	return len(env.Map())
+}
+
 func (env *Env) Init(src *Env) {
 	(*env) = make([]string, 0, len(*src))
 	for _, val := range *src {

+ 28 - 0
engine/env_test.go

@@ -4,6 +4,34 @@ import (
 	"testing"
 )
 
+func TestEnvLenZero(t *testing.T) {
+	env := &Env{}
+	if env.Len() != 0 {
+		t.Fatalf("%d", env.Len())
+	}
+}
+
+func TestEnvLenNotZero(t *testing.T) {
+	env := &Env{}
+	env.Set("foo", "bar")
+	env.Set("ga", "bu")
+	if env.Len() != 2 {
+		t.Fatalf("%d", env.Len())
+	}
+}
+
+func TestEnvLenDup(t *testing.T) {
+	env := &Env{
+		"foo=bar",
+		"foo=baz",
+		"a=b",
+	}
+	// len(env) != env.Len()
+	if env.Len() != 2 {
+		t.Fatalf("%d", env.Len())
+	}
+}
+
 func TestNewJob(t *testing.T) {
 	job := mkJob(t, "dummy", "--level=awesome")
 	if job.Name != "dummy" {

+ 4 - 0
engine/job.go

@@ -208,3 +208,7 @@ func (job *Job) Error(err error) Status {
 	fmt.Fprintf(job.Stderr, "%s\n", err)
 	return StatusErr
 }
+
+func (job *Job) StatusCode() int {
+	return int(job.status)
+}

+ 120 - 0
engine/remote.go

@@ -0,0 +1,120 @@
+package engine
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/pkg/beam"
+	"github.com/dotcloud/docker/pkg/beam/data"
+	"io"
+	"os"
+	"strconv"
+	"sync"
+)
+
+type Sender struct {
+	beam.Sender
+}
+
+func NewSender(s beam.Sender) *Sender {
+	return &Sender{s}
+}
+
+func (s *Sender) Install(eng *Engine) error {
+	// FIXME: this doesn't exist yet.
+	eng.RegisterCatchall(s.Handle)
+	return nil
+}
+
+func (s *Sender) Handle(job *Job) Status {
+	msg := data.Empty().Set("cmd", append([]string{job.Name}, job.Args...)...)
+	peer, err := beam.SendConn(s, msg.Bytes())
+	if err != nil {
+		return job.Errorf("beamsend: %v", err)
+	}
+	defer peer.Close()
+	var tasks sync.WaitGroup
+	defer tasks.Wait()
+	r := beam.NewRouter(nil)
+	r.NewRoute().KeyStartsWith("cmd", "log", "stdout").HasAttachment().Handler(func(p []byte, stdout *os.File) error {
+		tasks.Add(1)
+		io.Copy(job.Stdout, stdout)
+		tasks.Done()
+		return nil
+	})
+	r.NewRoute().KeyStartsWith("cmd", "log", "stderr").HasAttachment().Handler(func(p []byte, stderr *os.File) error {
+		tasks.Add(1)
+		io.Copy(job.Stderr, stderr)
+		tasks.Done()
+		return nil
+	})
+	r.NewRoute().KeyStartsWith("cmd", "log", "stdin").HasAttachment().Handler(func(p []byte, stdin *os.File) error {
+		tasks.Add(1)
+		io.Copy(stdin, job.Stdin)
+		tasks.Done()
+		return nil
+	})
+	var status int
+	r.NewRoute().KeyStartsWith("cmd", "status").Handler(func(p []byte, f *os.File) error {
+		cmd := data.Message(p).Get("cmd")
+		if len(cmd) != 2 {
+			return fmt.Errorf("usage: %s <0-127>", cmd[0])
+		}
+		s, err := strconv.ParseUint(cmd[1], 10, 8)
+		if err != nil {
+			return fmt.Errorf("usage: %s <0-127>", cmd[0])
+		}
+		status = int(s)
+		return nil
+
+	})
+	if _, err := beam.Copy(r, peer); err != nil {
+		return job.Errorf("%v", err)
+	}
+	return Status(status)
+}
+
+type Receiver struct {
+	*Engine
+	peer beam.Receiver
+}
+
+func NewReceiver(peer beam.Receiver) *Receiver {
+	return &Receiver{Engine: New(), peer: peer}
+}
+
+func (rcv *Receiver) Run() error {
+	r := beam.NewRouter(nil)
+	r.NewRoute().KeyExists("cmd").Handler(func(p []byte, f *os.File) error {
+		// Use the attachment as a beam return channel
+		peer, err := beam.FileConn(f)
+		if err != nil {
+			f.Close()
+			return err
+		}
+		cmd := data.Message(p).Get("cmd")
+		job := rcv.Engine.Job(cmd[0], cmd[1:]...)
+		stdout, err := beam.SendPipe(peer, data.Empty().Set("cmd", "log", "stdout").Bytes())
+		if err != nil {
+			return err
+		}
+		job.Stdout.Add(stdout)
+		stderr, err := beam.SendPipe(peer, data.Empty().Set("cmd", "log", "stderr").Bytes())
+		if err != nil {
+			return err
+		}
+		job.Stderr.Add(stderr)
+		stdin, err := beam.SendPipe(peer, data.Empty().Set("cmd", "log", "stdin").Bytes())
+		if err != nil {
+			return err
+		}
+		job.Stdin.Add(stdin)
+		// ignore error because we pass the raw status
+		job.Run()
+		err = peer.Send(data.Empty().Set("cmd", "status", fmt.Sprintf("%d", job.status)).Bytes(), nil)
+		if err != nil {
+			return err
+		}
+		return nil
+	})
+	_, err := beam.Copy(r, rcv.peer)
+	return err
+}

+ 3 - 0
engine/remote_test.go

@@ -0,0 +1,3 @@
+package engine
+
+import ()

+ 43 - 0
engine/rengine/main.go

@@ -0,0 +1,43 @@
+package main
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/engine"
+	"github.com/dotcloud/docker/pkg/beam"
+	"net"
+	"os"
+)
+
+func main() {
+	eng := engine.New()
+
+	c, err := net.Dial("unix", "beam.sock")
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%v\n", err)
+		return
+	}
+	defer c.Close()
+	f, err := c.(*net.UnixConn).File()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%v\n", err)
+		return
+	}
+
+	child, err := beam.FileConn(f)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%v\n", err)
+		return
+	}
+	defer child.Close()
+
+	sender := engine.NewSender(child)
+	sender.Install(eng)
+
+	cmd := eng.Job(os.Args[1], os.Args[2:]...)
+	cmd.Stdout.Add(os.Stdout)
+	cmd.Stderr.Add(os.Stderr)
+	if err := cmd.Run(); err != nil {
+		fmt.Fprintf(os.Stderr, "%v\n", err)
+		os.Exit(1)
+	}
+}

+ 119 - 0
engine/spawn/spawn.go

@@ -0,0 +1,119 @@
+package spawn
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/engine"
+	"github.com/dotcloud/docker/pkg/beam"
+	"github.com/dotcloud/docker/utils"
+	"os"
+	"os/exec"
+)
+
+var initCalled bool
+
+// Init checks if the current process has been created by Spawn.
+//
+// If no, it returns nil and the original program can continue
+// unmodified.
+//
+// If no, it hijacks the process to run as a child worker controlled
+// by its parent over a beam connection, with f exposed as a remote
+// service. In this case Init never returns.
+//
+// The hijacking process takes place as follows:
+//	- Open file descriptor 3 as a beam endpoint. If this fails,
+//	terminate the current process.
+//	- Start a new engine.
+//	- Call f.Install on the engine. Any handlers registered
+//	will be available for remote invocation by the parent.
+//	- Listen for beam messages from the parent and pass them to
+//	the handlers.
+//	- When the beam endpoint is closed by the parent, terminate
+//	the current process.
+//
+// NOTE: Init must be called at the beginning of the same program
+// calling Spawn. This is because Spawn approximates a "fork" by
+// re-executing the current binary - where it expects spawn.Init
+// to intercept the control flow and execute the worker code.
+func Init(f engine.Installer) error {
+	initCalled = true
+	if os.Getenv("ENGINESPAWN") != "1" {
+		return nil
+	}
+	fmt.Printf("[%d child]\n", os.Getpid())
+	// Hijack the process
+	childErr := func() error {
+		fd3 := os.NewFile(3, "beam-introspect")
+		introsp, err := beam.FileConn(fd3)
+		if err != nil {
+			return fmt.Errorf("beam introspection error: %v", err)
+		}
+		fd3.Close()
+		defer introsp.Close()
+		eng := engine.NewReceiver(introsp)
+		if err := f.Install(eng.Engine); err != nil {
+			return err
+		}
+		if err := eng.Run(); err != nil {
+			return err
+		}
+		return nil
+	}()
+	if childErr != nil {
+		os.Exit(1)
+	}
+	os.Exit(0)
+	return nil // Never reached
+}
+
+// Spawn starts a new Engine in a child process and returns
+// a proxy Engine through which it can be controlled.
+//
+// The commands available on the child engine are determined
+// by an earlier call to Init. It is important that Init be
+// called at the very beginning of the current program - this
+// allows it to be called as a re-execution hook in the child
+// process.
+//
+// Long story short, if you want to expose `myservice` in a child
+// process, do this:
+//
+// func main() {
+//     spawn.Init(myservice)
+//     [..]
+//     child, err := spawn.Spawn()
+//     [..]
+//     child.Job("dosomething").Run()
+// }
+func Spawn() (*engine.Engine, error) {
+	if !initCalled {
+		return nil, fmt.Errorf("spawn.Init must be called at the top of the main() function")
+	}
+	cmd := exec.Command(utils.SelfPath())
+	cmd.Env = append(cmd.Env, "ENGINESPAWN=1")
+	local, remote, err := beam.SocketPair()
+	if err != nil {
+		return nil, err
+	}
+	child, err := beam.FileConn(local)
+	if err != nil {
+		local.Close()
+		remote.Close()
+		return nil, err
+	}
+	local.Close()
+	cmd.ExtraFiles = append(cmd.ExtraFiles, remote)
+	// FIXME: the beam/engine glue has no way to inform the caller
+	// of the child's termination. The next call will simply return
+	// an error.
+	if err := cmd.Start(); err != nil {
+		child.Close()
+		return nil, err
+	}
+	eng := engine.New()
+	if err := engine.NewSender(child).Install(eng); err != nil {
+		child.Close()
+		return nil, err
+	}
+	return eng, nil
+}

+ 61 - 0
engine/spawn/subengine/main.go

@@ -0,0 +1,61 @@
+package main
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/engine"
+	"github.com/dotcloud/docker/engine/spawn"
+	"log"
+	"os"
+	"os/exec"
+	"strings"
+)
+
+func main() {
+	fmt.Printf("[%d] MAIN\n", os.Getpid())
+	spawn.Init(&Worker{})
+	fmt.Printf("[%d parent] spawning\n", os.Getpid())
+	eng, err := spawn.Spawn()
+	if err != nil {
+		log.Fatal(err)
+	}
+	fmt.Printf("[parent] spawned\n")
+	job := eng.Job(os.Args[1], os.Args[2:]...)
+	job.Stdout.Add(os.Stdout)
+	job.Stderr.Add(os.Stderr)
+	job.Run()
+	// FIXME: use the job's status code
+	os.Exit(0)
+}
+
+type Worker struct {
+}
+
+func (w *Worker) Install(eng *engine.Engine) error {
+	eng.Register("exec", w.Exec)
+	eng.Register("cd", w.Cd)
+	eng.Register("echo", w.Echo)
+	return nil
+}
+
+func (w *Worker) Exec(job *engine.Job) engine.Status {
+	fmt.Printf("--> %v\n", job.Args)
+	cmd := exec.Command(job.Args[0], job.Args[1:]...)
+	cmd.Stdout = job.Stdout
+	cmd.Stderr = os.Stderr
+	if err := cmd.Run(); err != nil {
+		return job.Errorf("%v\n", err)
+	}
+	return engine.StatusOK
+}
+
+func (w *Worker) Cd(job *engine.Job) engine.Status {
+	if err := os.Chdir(job.Args[0]); err != nil {
+		return job.Errorf("%v\n", err)
+	}
+	return engine.StatusOK
+}
+
+func (w *Worker) Echo(job *engine.Job) engine.Status {
+	fmt.Fprintf(job.Stdout, "%s\n", strings.Join(job.Args, " "))
+	return engine.StatusOK
+}

+ 22 - 0
integration-cli/docker_cli_run_test.go

@@ -665,3 +665,25 @@ func TestUnPrivilegedCannotMount(t *testing.T) {
 
 	logDone("run - test un-privileged cannot mount")
 }
+
+func TestSysNotAvaliableInNonPrivilegedContainers(t *testing.T) {
+	cmd := exec.Command(dockerBinary, "run", "busybox", "ls", "/sys/kernel")
+	if code, err := runCommand(cmd); err == nil || code == 0 {
+		t.Fatal("sys should not be available in a non privileged container")
+	}
+
+	deleteAllContainers()
+
+	logDone("run - sys not avaliable in non privileged container")
+}
+
+func TestSysAvaliableInPrivilegedContainers(t *testing.T) {
+	cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "ls", "/sys/kernel")
+	if code, err := runCommand(cmd); err != nil || code != 0 {
+		t.Fatalf("sys should be available in privileged container")
+	}
+
+	deleteAllContainers()
+
+	logDone("run - sys avaliable in privileged container")
+}

+ 4 - 0
integration/MAINTAINERS

@@ -0,0 +1,4 @@
+Solomon Hykes <s@docker.com>
+# WE ARE LOOKING FOR VOLUNTEERS TO HELP CLEAN THIS UP.
+# TO VOLUNTEER PLEASE OPEN A PULL REQUEST ADDING YOURSELF TO THIS FILE.
+# WE WILL HELP YOU GET STARTED. THANKS!

+ 23 - 0
integration/README.md

@@ -0,0 +1,23 @@
+## Legacy integration tests
+
+`./integration` contains Docker's legacy integration tests.
+It is DEPRECATED and will eventually be removed.
+
+### If you are a *CONTRIBUTOR* and want to add a test:
+
+* Consider mocking out side effects and contributing a *unit test* in the subsystem
+you're modifying. For example, the remote API has unit tests in `./api/server/server_unit_tests.go`.
+The events subsystem has unit tests in `./events/events_test.go`. And so on.
+
+* For end-to-end integration tests, please contribute to `./integration-cli`.
+
+
+### If you are a *MAINTAINER*
+
+Please don't allow patches adding new tests to `./integration`.
+
+### If you are *LOOKING FOR A WAY TO HELP*
+
+Please consider porting tests away from `./integration` and into either unit tests or CLI tests.
+
+Any help will be greatly appreciated!

+ 27 - 1
integration/container_test.go

@@ -407,7 +407,7 @@ func TestCopyVolumeUidGid(t *testing.T) {
 	defer r.Nuke()
 
 	// Add directory not owned by root
-	container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test.txt && chown daemon.daemon /hello"}, t)
+	container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test && chown daemon.daemon /hello"}, t)
 	defer r.Destroy(container1)
 
 	if container1.State.IsRunning() {
@@ -432,6 +432,32 @@ func TestCopyVolumeUidGid(t *testing.T) {
 	if !strings.Contains(stdout1, "daemon daemon") {
 		t.Fatal("Container failed to transfer uid and gid to volume")
 	}
+
+	container2, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && chown daemon.daemon /hello"}, t)
+	defer r.Destroy(container1)
+
+	if container2.State.IsRunning() {
+		t.Errorf("Container shouldn't be running")
+	}
+	if err := container2.Run(); err != nil {
+		t.Fatal(err)
+	}
+	if container2.State.IsRunning() {
+		t.Errorf("Container shouldn't be running")
+	}
+
+	img2, err := r.Commit(container2, "", "", "unit test commited image", "", nil)
+	if err != nil {
+		t.Error(err)
+	}
+
+	// Test that the uid and gid is copied from the image to the volume
+	tmpDir2 := tempDir(t)
+	defer os.RemoveAll(tmpDir2)
+	stdout2, _ := runContainer(eng, r, []string{"-v", "/hello", img2.ID, "stat", "-c", "%U %G", "/hello"}, t)
+	if !strings.Contains(stdout2, "daemon daemon") {
+		t.Fatal("Container failed to transfer uid and gid to volume")
+	}
 }
 
 // Test for #1582

+ 169 - 0
pkg/cgroups/fs/blkio_test.go

@@ -0,0 +1,169 @@
+package fs
+
+import (
+	"testing"
+)
+
+const (
+	sectorsRecursiveContents      = `8:0 1024`
+	serviceBytesRecursiveContents = `8:0 Read 100
+8:0 Write 400
+8:0 Sync 200
+8:0 Async 300
+8:0 Total 500
+Total 500`
+	servicedRecursiveContents = `8:0 Read 10
+8:0 Write 40
+8:0 Sync 20
+8:0 Async 30
+8:0 Total 50
+Total 50`
+	queuedRecursiveContents = `8:0 Read 1
+8:0 Write 4
+8:0 Sync 2
+8:0 Async 3
+8:0 Total 5
+Total 5`
+)
+
+func TestBlkioStats(t *testing.T) {
+	helper := NewCgroupTestUtil("blkio", t)
+	defer helper.cleanup()
+	helper.writeFileContents(map[string]string{
+		"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
+		"blkio.io_serviced_recursive":      servicedRecursiveContents,
+		"blkio.io_queued_recursive":        queuedRecursiveContents,
+		"blkio.sectors_recursive":          sectorsRecursiveContents,
+	})
+
+	blkio := &blkioGroup{}
+	stats, err := blkio.Stats(helper.CgroupData)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Verify expected stats.
+	expectedStats := map[string]float64{
+		"blkio.sectors_recursive:8:0": 1024.0,
+
+		// Serviced bytes.
+		"io_service_bytes_recursive:8:0:Read":  100.0,
+		"io_service_bytes_recursive:8:0:Write": 400.0,
+		"io_service_bytes_recursive:8:0:Sync":  200.0,
+		"io_service_bytes_recursive:8:0:Async": 300.0,
+		"io_service_bytes_recursive:8:0:Total": 500.0,
+
+		// Serviced requests.
+		"io_serviced_recursive:8:0:Read":  10.0,
+		"io_serviced_recursive:8:0:Write": 40.0,
+		"io_serviced_recursive:8:0:Sync":  20.0,
+		"io_serviced_recursive:8:0:Async": 30.0,
+		"io_serviced_recursive:8:0:Total": 50.0,
+
+		// Queued requests.
+		"io_queued_recursive:8:0:Read":  1.0,
+		"io_queued_recursive:8:0:Write": 4.0,
+		"io_queued_recursive:8:0:Sync":  2.0,
+		"io_queued_recursive:8:0:Async": 3.0,
+		"io_queued_recursive:8:0:Total": 5.0,
+	}
+	expectStats(t, expectedStats, stats)
+}
+
+func TestBlkioStatsNoSectorsFile(t *testing.T) {
+	helper := NewCgroupTestUtil("blkio", t)
+	defer helper.cleanup()
+	helper.writeFileContents(map[string]string{
+		"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
+		"blkio.io_serviced_recursive":      servicedRecursiveContents,
+		"blkio.io_queued_recursive":        queuedRecursiveContents,
+	})
+
+	blkio := &blkioGroup{}
+	_, err := blkio.Stats(helper.CgroupData)
+	if err == nil {
+		t.Fatal("Expected to fail, but did not")
+	}
+}
+
+func TestBlkioStatsNoServiceBytesFile(t *testing.T) {
+	helper := NewCgroupTestUtil("blkio", t)
+	defer helper.cleanup()
+	helper.writeFileContents(map[string]string{
+		"blkio.io_serviced_recursive": servicedRecursiveContents,
+		"blkio.io_queued_recursive":   queuedRecursiveContents,
+		"blkio.sectors_recursive":     sectorsRecursiveContents,
+	})
+
+	blkio := &blkioGroup{}
+	_, err := blkio.Stats(helper.CgroupData)
+	if err == nil {
+		t.Fatal("Expected to fail, but did not")
+	}
+}
+
+func TestBlkioStatsNoServicedFile(t *testing.T) {
+	helper := NewCgroupTestUtil("blkio", t)
+	defer helper.cleanup()
+	helper.writeFileContents(map[string]string{
+		"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
+		"blkio.io_queued_recursive":        queuedRecursiveContents,
+		"blkio.sectors_recursive":          sectorsRecursiveContents,
+	})
+
+	blkio := &blkioGroup{}
+	_, err := blkio.Stats(helper.CgroupData)
+	if err == nil {
+		t.Fatal("Expected to fail, but did not")
+	}
+}
+
+func TestBlkioStatsNoQueuedFile(t *testing.T) {
+	helper := NewCgroupTestUtil("blkio", t)
+	defer helper.cleanup()
+	helper.writeFileContents(map[string]string{
+		"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
+		"blkio.io_serviced_recursive":      servicedRecursiveContents,
+		"blkio.sectors_recursive":          sectorsRecursiveContents,
+	})
+
+	blkio := &blkioGroup{}
+	_, err := blkio.Stats(helper.CgroupData)
+	if err == nil {
+		t.Fatal("Expected to fail, but did not")
+	}
+}
+
+func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) {
+	helper := NewCgroupTestUtil("blkio", t)
+	defer helper.cleanup()
+	helper.writeFileContents(map[string]string{
+		"blkio.io_service_bytes_recursive": "8:0 Read 100 100",
+		"blkio.io_serviced_recursive":      servicedRecursiveContents,
+		"blkio.io_queued_recursive":        queuedRecursiveContents,
+		"blkio.sectors_recursive":          sectorsRecursiveContents,
+	})
+
+	blkio := &blkioGroup{}
+	_, err := blkio.Stats(helper.CgroupData)
+	if err == nil {
+		t.Fatal("Expected to fail, but did not")
+	}
+}
+
+func TestBlkioStatsUnexpectedFieldType(t *testing.T) {
+	helper := NewCgroupTestUtil("blkio", t)
+	defer helper.cleanup()
+	helper.writeFileContents(map[string]string{
+		"blkio.io_service_bytes_recursive": "8:0 Read Write",
+		"blkio.io_serviced_recursive":      servicedRecursiveContents,
+		"blkio.io_queued_recursive":        queuedRecursiveContents,
+		"blkio.sectors_recursive":          sectorsRecursiveContents,
+	})
+
+	blkio := &blkioGroup{}
+	_, err := blkio.Stats(helper.CgroupData)
+	if err == nil {
+		t.Fatal("Expected to fail, but did not")
+	}
+}

+ 19 - 3
pkg/cgroups/fs/memory.go

@@ -2,6 +2,7 @@ package fs
 
 import (
 	"bufio"
+	"fmt"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -56,13 +57,14 @@ func (s *memoryGroup) Stats(d *data) (map[string]float64, error) {
 		return nil, err
 	}
 
-	f, err := os.Open(filepath.Join(path, "memory.stat"))
+	// Set stats from memory.stat.
+	statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
 	if err != nil {
 		return nil, err
 	}
-	defer f.Close()
+	defer statsFile.Close()
 
-	sc := bufio.NewScanner(f)
+	sc := bufio.NewScanner(statsFile)
 	for sc.Scan() {
 		t, v, err := getCgroupParamKeyValue(sc.Text())
 		if err != nil {
@@ -70,5 +72,19 @@ func (s *memoryGroup) Stats(d *data) (map[string]float64, error) {
 		}
 		paramData[t] = v
 	}
+
+	// Set memory usage and max historical usage.
+	params := []string{
+		"usage_in_bytes",
+		"max_usage_in_bytes",
+	}
+	for _, param := range params {
+		value, err := getCgroupParamFloat64(path, fmt.Sprintf("memory.%s", param))
+		if err != nil {
+			return nil, err
+		}
+		paramData[param] = value
+	}
+
 	return paramData, nil
 }

+ 75 - 0
pkg/cgroups/fs/test_util.go

@@ -0,0 +1,75 @@
+/*
+Utility for testing cgroup operations.
+
+Creates a mock of the cgroup filesystem for the duration of the test.
+*/
+package fs
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"testing"
+)
+
+type cgroupTestUtil struct {
+	// data to use in tests.
+	CgroupData *data
+
+	// Path to the mock cgroup directory.
+	CgroupPath string
+
+	// Temporary directory to store mock cgroup filesystem.
+	tempDir string
+	t       *testing.T
+}
+
+// Creates a new test util for the specified subsystem
+func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil {
+	d := &data{}
+	tempDir, err := ioutil.TempDir("", fmt.Sprintf("%s_cgroup_test", subsystem))
+	if err != nil {
+		t.Fatal(err)
+	}
+	d.root = tempDir
+	testCgroupPath, err := d.path(subsystem)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Ensure the full mock cgroup path exists.
+	err = os.MkdirAll(testCgroupPath, 0755)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return &cgroupTestUtil{CgroupData: d, CgroupPath: testCgroupPath, tempDir: tempDir, t: t}
+}
+
+func (c *cgroupTestUtil) cleanup() {
+	os.RemoveAll(c.tempDir)
+}
+
+// Write the specified contents on the mock of the specified cgroup files.
+func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) {
+	for file, contents := range fileContents {
+		err := writeFile(c.CgroupPath, file, contents)
+		if err != nil {
+			c.t.Fatal(err)
+		}
+	}
+}
+
+// Expect the specified stats.
+func expectStats(t *testing.T, expected, actual map[string]float64) {
+	for stat, expectedValue := range expected {
+		actualValue, ok := actual[stat]
+		if !ok {
+			log.Printf("Expected stat %s to exist: %s", stat, actual)
+			t.Fail()
+		} else if actualValue != expectedValue {
+			log.Printf("Expected stats %s to have value %f but had %f instead", stat, expectedValue, actualValue)
+			t.Fail()
+		}
+	}
+}

+ 11 - 0
pkg/cgroups/fs/utils.go

@@ -3,6 +3,8 @@ package fs
 import (
 	"errors"
 	"fmt"
+	"io/ioutil"
+	"path/filepath"
 	"strconv"
 	"strings"
 )
@@ -27,3 +29,12 @@ func getCgroupParamKeyValue(t string) (string, float64, error) {
 		return "", 0.0, ErrNotValidFormat
 	}
 }
+
+// Gets a single float64 value from the specified cgroup file.
+func getCgroupParamFloat64(cgroupPath, cgroupFile string) (float64, error) {
+	contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile))
+	if err != nil {
+		return -1.0, err
+	}
+	return strconv.ParseFloat(strings.TrimSpace(string(contents)), 64)
+}

+ 68 - 0
pkg/cgroups/fs/utils_test.go

@@ -0,0 +1,68 @@
+package fs
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+const (
+	cgroupFile  = "cgroup.file"
+	floatValue  = 2048.0
+	floatString = "2048"
+)
+
+func TestGetCgroupParamsFloat64(t *testing.T) {
+	// Setup tempdir.
+	tempDir, err := ioutil.TempDir("", "cgroup_utils_test")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tempDir)
+	tempFile := filepath.Join(tempDir, cgroupFile)
+
+	// Success.
+	err = ioutil.WriteFile(tempFile, []byte(floatString), 0755)
+	if err != nil {
+		t.Fatal(err)
+	}
+	value, err := getCgroupParamFloat64(tempDir, cgroupFile)
+	if err != nil {
+		t.Fatal(err)
+	} else if value != floatValue {
+		t.Fatalf("Expected %f to equal %f", value, floatValue)
+	}
+
+	// Success with new line.
+	err = ioutil.WriteFile(tempFile, []byte(floatString+"\n"), 0755)
+	if err != nil {
+		t.Fatal(err)
+	}
+	value, err = getCgroupParamFloat64(tempDir, cgroupFile)
+	if err != nil {
+		t.Fatal(err)
+	} else if value != floatValue {
+		t.Fatalf("Expected %f to equal %f", value, floatValue)
+	}
+
+	// Not a float.
+	err = ioutil.WriteFile(tempFile, []byte("not-a-float"), 0755)
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, err = getCgroupParamFloat64(tempDir, cgroupFile)
+	if err == nil {
+		t.Fatal("Expecting error, got none")
+	}
+
+	// Unknown file.
+	err = os.Remove(tempFile)
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, err = getCgroupParamFloat64(tempDir, cgroupFile)
+	if err == nil {
+		t.Fatal("Expecting error, got none")
+	}
+}

+ 88 - 75
pkg/libcontainer/README.md

@@ -16,135 +16,148 @@ process are specified in this file.  The configuration is used for each process
 Sample `container.json` file:
 ```json
 {
-   "hostname" : "koye",
-   "networks" : [
+   "mounts" : [
       {
-         "gateway" : "172.17.42.1",
-         "context" : {
-            "bridge" : "docker0",
-            "prefix" : "veth"
-         },
-         "address" : "172.17.0.2/16",
-         "type" : "veth",
-         "mtu" : 1500
+         "type" : "devtmpfs"
       }
    ],
-   "cgroups" : {
-      "parent" : "docker",
-      "name" : "11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620"
-   },
    "tty" : true,
    "environment" : [
       "HOME=/",
-      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
-      "HOSTNAME=11bb30683fb0",
-      "TERM=xterm"
+      "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin",
+      "container=docker",
+      "TERM=xterm-256color"
    ],
+   "hostname" : "koye",
+   "cgroups" : {
+      "parent" : "docker",
+      "name" : "docker-koye"
+   },
    "capabilities_mask" : [
       {
-        "key": "SETPCAP",
-        "enabled": false
+         "value" : 8,
+         "key" : "SETPCAP",
+         "enabled" : false
       },
       {
-        "key": "SYS_MODULE",
-        "enabled": false
+         "enabled" : false,
+         "value" : 16,
+         "key" : "SYS_MODULE"
       },
       {
-        "key": "SYS_RAWIO",
-        "enabled": false
+         "value" : 17,
+         "key" : "SYS_RAWIO",
+         "enabled" : false
       },
       {
-        "key": "SYS_PACCT",
-        "enabled": false
+         "key" : "SYS_PACCT",
+         "value" : 20,
+         "enabled" : false
       },
       {
-        "key": "SYS_ADMIN",
-        "enabled": false
+         "value" : 21,
+         "key" : "SYS_ADMIN",
+         "enabled" : false
       },
       {
-        "key": "SYS_NICE",
-        "enabled": false
+         "value" : 23,
+         "key" : "SYS_NICE",
+         "enabled" : false
       },
       {
-        "key": "SYS_RESOURCE",
-        "enabled": false
+         "value" : 24,
+         "key" : "SYS_RESOURCE",
+         "enabled" : false
       },
       {
-        "key": "SYS_TIME",
-        "enabled": false
+         "key" : "SYS_TIME",
+         "value" : 25,
+         "enabled" : false
       },
       {
-        "key": "SYS_TTY_CONFIG",
-        "enabled": false
+         "enabled" : false,
+         "value" : 26,
+         "key" : "SYS_TTY_CONFIG"
       },
       {
-        "key": "MKNOD",
-        "enabled": true
+         "key" : "AUDIT_WRITE",
+         "value" : 29,
+         "enabled" : false
       },
       {
-        "key": "AUDIT_WRITE",
-        "enabled": false
+         "value" : 30,
+         "key" : "AUDIT_CONTROL",
+         "enabled" : false
       },
       {
-        "key": "AUDIT_CONTROL",
-        "enabled": false
+         "enabled" : false,
+         "key" : "MAC_OVERRIDE",
+         "value" : 32
       },
       {
-        "key": "MAC_OVERRIDE",
-        "enabled": false
+         "enabled" : false,
+         "key" : "MAC_ADMIN",
+         "value" : 33
       },
       {
-        "key": "MAC_ADMIN",
-        "enabled": false
+         "key" : "NET_ADMIN",
+         "value" : 12,
+         "enabled" : false
       },
       {
-        "key": "NET_ADMIN",
-        "enabled": false
+         "value" : 27,
+         "key" : "MKNOD",
+         "enabled" : true
       }
    ],
-   "context" : {
-      "apparmor_profile" : "docker-default"
-   },
-   "mounts" : [
-      {
-         "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/resolv.conf",
-         "writable" : false,
-         "destination" : "/etc/resolv.conf",
-         "private" : true
-      },
+   "networks" : [
       {
-         "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/hostname",
-         "writable" : false,
-         "destination" : "/etc/hostname",
-         "private" : true
+         "mtu" : 1500,
+         "address" : "127.0.0.1/0",
+         "type" : "loopback",
+         "gateway" : "localhost"
       },
       {
-         "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/hosts",
-         "writable" : false,
-         "destination" : "/etc/hosts",
-         "private" : true
+         "mtu" : 1500,
+         "address" : "172.17.42.2/16",
+         "type" : "veth",
+         "context" : {
+            "bridge" : "docker0",
+            "prefix" : "veth"
+         },
+         "gateway" : "172.17.42.1"
       }
    ],
    "namespaces" : [
       {
-        "key": "NEWNS",
-        "enabled": true
+         "key" : "NEWNS",
+         "value" : 131072,
+         "enabled" : true,
+         "file" : "mnt"
       },
       {
-        "key": "NEWUTS",
-        "enabled": true
+         "key" : "NEWUTS",
+         "value" : 67108864,
+         "enabled" : true,
+         "file" : "uts"
       },
       {
-        "key": "NEWIPC",
-        "enabled": true
+         "enabled" : true,
+         "file" : "ipc",
+         "key" : "NEWIPC",
+         "value" : 134217728
       },
       {
-        "key": "NEWPID",
-        "enabled": true
+         "file" : "pid",
+         "enabled" : true,
+         "value" : 536870912,
+         "key" : "NEWPID"
       },
       {
-        "key": "NEWNET",
-        "enabled": true
+         "enabled" : true,
+         "file" : "net",
+         "key" : "NEWNET",
+         "value" : 1073741824
       }
    ]
 }

+ 60 - 0
pkg/libcontainer/console/console.go

@@ -0,0 +1,60 @@
+// +build linux
+
+package console
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/pkg/label"
+	"github.com/dotcloud/docker/pkg/system"
+	"os"
+	"path/filepath"
+	"syscall"
+)
+
+// Setup initializes the proper /dev/console inside the rootfs path
+func Setup(rootfs, consolePath, mountLabel string) error {
+	oldMask := system.Umask(0000)
+	defer system.Umask(oldMask)
+
+	stat, err := os.Stat(consolePath)
+	if err != nil {
+		return fmt.Errorf("stat console %s %s", consolePath, err)
+	}
+	var (
+		st   = stat.Sys().(*syscall.Stat_t)
+		dest = filepath.Join(rootfs, "dev/console")
+	)
+	if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
+		return fmt.Errorf("remove %s %s", dest, err)
+	}
+	if err := os.Chmod(consolePath, 0600); err != nil {
+		return err
+	}
+	if err := os.Chown(consolePath, 0, 0); err != nil {
+		return err
+	}
+	if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil {
+		return fmt.Errorf("mknod %s %s", dest, err)
+	}
+	if err := label.SetFileLabel(consolePath, mountLabel); err != nil {
+		return fmt.Errorf("set file label %s %s", dest, err)
+	}
+	if err := system.Mount(consolePath, dest, "bind", syscall.MS_BIND, ""); err != nil {
+		return fmt.Errorf("bind %s to %s %s", consolePath, dest, err)
+	}
+	return nil
+}
+
+func OpenAndDup(consolePath string) error {
+	slave, err := system.OpenTerminal(consolePath, syscall.O_RDWR)
+	if err != nil {
+		return fmt.Errorf("open terminal %s", err)
+	}
+	if err := system.Dup2(slave.Fd(), 0); err != nil {
+		return err
+	}
+	if err := system.Dup2(slave.Fd(), 1); err != nil {
+		return err
+	}
+	return system.Dup2(slave.Fd(), 2)
+}

+ 1 - 10
pkg/libcontainer/container.go

@@ -23,7 +23,7 @@ type Container struct {
 	Networks         []*Network      `json:"networks,omitempty"`          // nil for host's network stack
 	Cgroups          *cgroups.Cgroup `json:"cgroups,omitempty"`           // cgroups
 	Context          Context         `json:"context,omitempty"`           // generic context for specific options (apparmor, selinux)
-	Mounts           []Mount         `json:"mounts,omitempty"`
+	Mounts           Mounts          `json:"mounts,omitempty"`
 }
 
 // Network defines configuration for a container's networking stack
@@ -37,12 +37,3 @@ type Network struct {
 	Gateway string  `json:"gateway,omitempty"`
 	Mtu     int     `json:"mtu,omitempty"`
 }
-
-// Bind mounts from the host system to the container
-//
-type Mount struct {
-	Source      string `json:"source"`      // Source path, in the host namespace
-	Destination string `json:"destination"` // Destination path, in the container
-	Writable    bool   `json:"writable"`
-	Private     bool   `json:"private"`
-}

+ 144 - 127
pkg/libcontainer/container.json

@@ -1,129 +1,146 @@
 {
-    "hostname": "koye",
-    "tty": true,
-    "environment": [
-        "HOME=/",
-        "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin",
-        "container=docker",
-        "TERM=xterm-256color"
-    ],
-    "namespaces": [
-    	{
-            "file": "ipc",
-            "value": 134217728,
-       	    "enabled": true,
-	    "key": "NEWIPC"
-	},
-    	{
-            "file": "mnt",
-            "value": 131072,
-      	    "enabled": true,
-	    "key": "NEWNS"
-	},
-    	{
-            "file": "pid",
-            "value": 536870912,
-       	    "enabled": true,
-	    "key": "NEWPID"
-	},
-    	{
-            "file": "uts",
-            "value": 67108864,
-            "enabled": true,
-	    "key": "NEWUTS"
-	},
-    	{
-            "file": "net",
-            "value": 1073741824,
-       	    "enabled": true,
-	    "key": "NEWNET"
-	}
-    ],
-    "capabilities_mask": [
-    	{
-	    "key": "SETPCAP",
-       	    "enabled": false
-	},
-    	{ 
-	    "key": "SYS_MODULE",
-            "enabled": false
-	},
-	{ 
-	    "key": "SYS_RAWIO",
-            "enabled": false
-	},
-	{ 
-	    "key": "SYS_PACCT",
-            "enabled": false
-	},
-	{ 
-	    "key": "SYS_ADMIN",
-            "enabled": false
-	},
-	{ 
-	    "key": "SYS_NICE",
-            "enabled": false
-	},
-	{ 
-	    "key": "SYS_RESOURCE",
-            "enabled": false
-	},
-	{ 
-	    "key": "SYS_TIME",
-            "enabled": false
-	},
-	{ 
-	    "key": "SYS_TTY_CONFIG",
-            "enabled": false
-	},
-	{ 
-	    "key": "MKNOD",
-            "enabled": true
-	},
-	{ 
-	    "key": "AUDIT_WRITE",
-            "enabled": false
-	},
-	{ 
-	    "key": "AUDIT_CONTROL",
-            "enabled": false
-	},
-	{ 
-	    "key": "MAC_OVERRIDE",
-            "enabled": false
-	},
-	{ 
-	    "key": "MAC_ADMIN",
-            "enabled": false
-	},
-	{ 
-	    "key": "NET_ADMIN",
-	    "enabled": false
-       	}
-    ],
-    "networks": [
-    	{
-	    "type": "loopback",
-	    "gateway": "localhost",
-	    "address": "127.0.0.1/0",
-	    "mtu": 1500
-	},
-    	{
-            "type": "veth",
-	    "gateway": "172.17.42.1",
-	    "address": "172.17.0.4/16",
-            "context": {
-                "prefix": "dock",
-                "bridge": "docker0"
-            },
-            "mtu": 1500
-        }
-    ],
-    "cgroups": {
-        "name": "docker-koye",
-        "parent": "docker",
-        "memory": 5248000,
-	"memory_swap": -1,
-	"cpu_shares": 1024
-    }
+   "mounts" : [
+      {
+         "type" : "devtmpfs"
+      }
+   ],
+   "tty" : true,
+   "environment" : [
+      "HOME=/",
+      "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin",
+      "container=docker",
+      "TERM=xterm-256color"
+   ],
+   "hostname" : "koye",
+   "cgroups" : {
+      "parent" : "docker",
+      "name" : "docker-koye"
+   },
+   "capabilities_mask" : [
+      {
+         "value" : 8,
+         "key" : "SETPCAP",
+         "enabled" : false
+      },
+      {
+         "enabled" : false,
+         "value" : 16,
+         "key" : "SYS_MODULE"
+      },
+      {
+         "value" : 17,
+         "key" : "SYS_RAWIO",
+         "enabled" : false
+      },
+      {
+         "key" : "SYS_PACCT",
+         "value" : 20,
+         "enabled" : false
+      },
+      {
+         "value" : 21,
+         "key" : "SYS_ADMIN",
+         "enabled" : false
+      },
+      {
+         "value" : 23,
+         "key" : "SYS_NICE",
+         "enabled" : false
+      },
+      {
+         "value" : 24,
+         "key" : "SYS_RESOURCE",
+         "enabled" : false
+      },
+      {
+         "key" : "SYS_TIME",
+         "value" : 25,
+         "enabled" : false
+      },
+      {
+         "enabled" : false,
+         "value" : 26,
+         "key" : "SYS_TTY_CONFIG"
+      },
+      {
+         "key" : "AUDIT_WRITE",
+         "value" : 29,
+         "enabled" : false
+      },
+      {
+         "value" : 30,
+         "key" : "AUDIT_CONTROL",
+         "enabled" : false
+      },
+      {
+         "enabled" : false,
+         "key" : "MAC_OVERRIDE",
+         "value" : 32
+      },
+      {
+         "enabled" : false,
+         "key" : "MAC_ADMIN",
+         "value" : 33
+      },
+      {
+         "key" : "NET_ADMIN",
+         "value" : 12,
+         "enabled" : false
+      },
+      {
+         "value" : 27,
+         "key" : "MKNOD",
+         "enabled" : true
+      }
+   ],
+   "networks" : [
+      {
+         "mtu" : 1500,
+         "address" : "127.0.0.1/0",
+         "type" : "loopback",
+         "gateway" : "localhost"
+      },
+      {
+         "mtu" : 1500,
+         "address" : "172.17.42.2/16",
+         "type" : "veth",
+         "context" : {
+            "bridge" : "docker0",
+            "prefix" : "veth"
+         },
+         "gateway" : "172.17.42.1"
+      }
+   ],
+   "namespaces" : [
+      {
+         "key" : "NEWNS",
+         "value" : 131072,
+         "enabled" : true,
+         "file" : "mnt"
+      },
+      {
+         "key" : "NEWUTS",
+         "value" : 67108864,
+         "enabled" : true,
+         "file" : "uts"
+      },
+      {
+         "enabled" : true,
+         "file" : "ipc",
+         "key" : "NEWIPC",
+         "value" : 134217728
+      },
+      {
+         "file" : "pid",
+         "enabled" : true,
+         "value" : 536870912,
+         "key" : "NEWPID"
+      },
+      {
+         "enabled" : true,
+         "file" : "net",
+         "key" : "NEWNET",
+         "value" : 1073741824
+      }
+   ]
 }

+ 0 - 10
pkg/libcontainer/container_test.go

@@ -56,14 +56,4 @@ func TestContainerJsonFormat(t *testing.T) {
 		t.Log("capabilities mask should not contain SYS_CHROOT")
 		t.Fail()
 	}
-
-	if container.Cgroups.CpuShares != 1024 {
-		t.Log("cpu shares not set correctly")
-		t.Fail()
-	}
-
-	if container.Cgroups.Memory != 5248000 {
-		t.Log("memory limit not set correctly")
-		t.Fail()
-	}
 }

+ 143 - 0
pkg/libcontainer/mount/init.go

@@ -0,0 +1,143 @@
+// +build linux
+
+package mount
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/pkg/label"
+	"github.com/dotcloud/docker/pkg/libcontainer"
+	"github.com/dotcloud/docker/pkg/libcontainer/mount/nodes"
+	"github.com/dotcloud/docker/pkg/libcontainer/security/restrict"
+	"github.com/dotcloud/docker/pkg/system"
+	"os"
+	"path/filepath"
+	"syscall"
+)
+
+// default mount point flags
+const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
+
+type mount struct {
+	source string
+	path   string
+	device string
+	flags  int
+	data   string
+}
+
+// InitializeMountNamespace setups up the devices, mount points, and filesystems for use inside a
+// new mount namepsace
+func InitializeMountNamespace(rootfs, console string, container *libcontainer.Container) error {
+	var (
+		err  error
+		flag = syscall.MS_PRIVATE
+	)
+	if container.NoPivotRoot {
+		flag = syscall.MS_SLAVE
+	}
+	if err := system.Mount("", "/", "", uintptr(flag|syscall.MS_REC), ""); err != nil {
+		return fmt.Errorf("mounting / as slave %s", err)
+	}
+	if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
+		return fmt.Errorf("mouting %s as bind %s", rootfs, err)
+	}
+	if err := mountSystem(rootfs, container); err != nil {
+		return fmt.Errorf("mount system %s", err)
+	}
+	if err := setupBindmounts(rootfs, container.Mounts); err != nil {
+		return fmt.Errorf("bind mounts %s", err)
+	}
+	if err := nodes.CopyN(rootfs, nodes.DefaultNodes); err != nil {
+		return fmt.Errorf("copy dev nodes %s", err)
+	}
+	if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" {
+		if err := restrict.Restrict(rootfs, restrictionPath); err != nil {
+			return fmt.Errorf("restrict %s", err)
+		}
+	}
+	if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil {
+		return err
+	}
+	if err := system.Chdir(rootfs); err != nil {
+		return fmt.Errorf("chdir into %s %s", rootfs, err)
+	}
+
+	if container.NoPivotRoot {
+		err = MsMoveRoot(rootfs)
+	} else {
+		err = PivotRoot(rootfs)
+	}
+	if err != nil {
+		return err
+	}
+
+	if container.ReadonlyFs {
+		if err := SetReadonly(); err != nil {
+			return fmt.Errorf("set readonly %s", err)
+		}
+	}
+
+	system.Umask(0022)
+
+	return nil
+}
+
+// mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts
+// inside the mount namespace
+func mountSystem(rootfs string, container *libcontainer.Container) error {
+	for _, m := range newSystemMounts(rootfs, container.Context["mount_label"], container.Mounts) {
+		if err := os.MkdirAll(m.path, 0755); err != nil && !os.IsExist(err) {
+			return fmt.Errorf("mkdirall %s %s", m.path, err)
+		}
+		if err := system.Mount(m.source, m.path, m.device, uintptr(m.flags), m.data); err != nil {
+			return fmt.Errorf("mounting %s into %s %s", m.source, m.path, err)
+		}
+	}
+	return nil
+}
+
+func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error {
+	for _, m := range bindMounts.OfType("bind") {
+		var (
+			flags = syscall.MS_BIND | syscall.MS_REC
+			dest  = filepath.Join(rootfs, m.Destination)
+		)
+		if !m.Writable {
+			flags = flags | syscall.MS_RDONLY
+		}
+		if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil {
+			return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err)
+		}
+		if !m.Writable {
+			if err := system.Mount(m.Source, dest, "bind", uintptr(flags|syscall.MS_REMOUNT), ""); err != nil {
+				return fmt.Errorf("remounting %s into %s %s", m.Source, dest, err)
+			}
+		}
+		if m.Private {
+			if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil {
+				return fmt.Errorf("mounting %s private %s", dest, err)
+			}
+		}
+	}
+	return nil
+}
+
+// TODO: this is crappy right now and should be cleaned up with a better way of handling system and
+// standard bind mounts allowing them to be more dymanic
+func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount {
+	systemMounts := []mount{
+		{source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags},
+	}
+
+	if len(mounts.OfType("devtmpfs")) == 1 {
+		systemMounts = append(systemMounts, mount{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: "mode=755"})
+	}
+	systemMounts = append(systemMounts,
+		mount{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)},
+		mount{source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)})
+
+	if len(mounts.OfType("sysfs")) == 1 {
+		systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags})
+	}
+	return systemMounts
+}

+ 19 - 0
pkg/libcontainer/mount/msmoveroot.go

@@ -0,0 +1,19 @@
+// +build linux
+
+package mount
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/pkg/system"
+	"syscall"
+)
+
+func MsMoveRoot(rootfs string) error {
+	if err := system.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil {
+		return fmt.Errorf("mount move %s into / %s", rootfs, err)
+	}
+	if err := system.Chroot("."); err != nil {
+		return fmt.Errorf("chroot . %s", err)
+	}
+	return system.Chdir("/")
+}

+ 49 - 0
pkg/libcontainer/mount/nodes/nodes.go

@@ -0,0 +1,49 @@
+// +build linux
+
+package nodes
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/pkg/system"
+	"os"
+	"path/filepath"
+	"syscall"
+)
+
+// Default list of device nodes to copy
+var DefaultNodes = []string{
+	"null",
+	"zero",
+	"full",
+	"random",
+	"urandom",
+	"tty",
+}
+
+// CopyN copies the device node from the host into the rootfs
+func CopyN(rootfs string, nodesToCopy []string) error {
+	oldMask := system.Umask(0000)
+	defer system.Umask(oldMask)
+
+	for _, node := range nodesToCopy {
+		if err := Copy(rootfs, node); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func Copy(rootfs, node string) error {
+	stat, err := os.Stat(filepath.Join("/dev", node))
+	if err != nil {
+		return err
+	}
+	var (
+		dest = filepath.Join(rootfs, "dev", node)
+		st   = stat.Sys().(*syscall.Stat_t)
+	)
+	if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) {
+		return fmt.Errorf("copy %s %s", node, err)
+	}
+	return nil
+}

+ 31 - 0
pkg/libcontainer/mount/pivotroot.go

@@ -0,0 +1,31 @@
+// +build linux
+
+package mount
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/pkg/system"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"syscall"
+)
+
+func PivotRoot(rootfs string) error {
+	pivotDir, err := ioutil.TempDir(rootfs, ".pivot_root")
+	if err != nil {
+		return fmt.Errorf("can't create pivot_root dir %s", pivotDir, err)
+	}
+	if err := system.Pivotroot(rootfs, pivotDir); err != nil {
+		return fmt.Errorf("pivot_root %s", err)
+	}
+	if err := system.Chdir("/"); err != nil {
+		return fmt.Errorf("chdir / %s", err)
+	}
+	// path to pivot dir now changed, update
+	pivotDir = filepath.Join("/", filepath.Base(pivotDir))
+	if err := system.Unmount(pivotDir, syscall.MNT_DETACH); err != nil {
+		return fmt.Errorf("unmount pivot_root dir %s", err)
+	}
+	return os.Remove(pivotDir)
+}

+ 26 - 0
pkg/libcontainer/mount/ptmx.go

@@ -0,0 +1,26 @@
+// +build linux
+
+package mount
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/pkg/libcontainer/console"
+	"os"
+	"path/filepath"
+)
+
+func SetupPtmx(rootfs, consolePath, mountLabel string) error {
+	ptmx := filepath.Join(rootfs, "dev/ptmx")
+	if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) {
+		return err
+	}
+	if err := os.Symlink("pts/ptmx", ptmx); err != nil {
+		return fmt.Errorf("symlink dev ptmx %s", err)
+	}
+	if consolePath != "" {
+		if err := console.Setup(rootfs, consolePath, mountLabel); err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 12 - 0
pkg/libcontainer/mount/readonly.go

@@ -0,0 +1,12 @@
+// +build linux
+
+package mount
+
+import (
+	"github.com/dotcloud/docker/pkg/system"
+	"syscall"
+)
+
+func SetReadonly() error {
+	return system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, "")
+}

+ 31 - 0
pkg/libcontainer/mount/remount.go

@@ -0,0 +1,31 @@
+// +build linux
+
+package mount
+
+import (
+	"github.com/dotcloud/docker/pkg/system"
+	"syscall"
+)
+
+func RemountProc() error {
+	if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil {
+		return err
+	}
+	if err := system.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), ""); err != nil {
+		return err
+	}
+	return nil
+}
+
+func RemountSys() error {
+	if err := system.Unmount("/sys", syscall.MNT_DETACH); err != nil {
+		if err != syscall.EINVAL {
+			return err
+		}
+	} else {
+		if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaultMountFlags), ""); err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 3 - 2
pkg/libcontainer/nsinit/execin.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"github.com/dotcloud/docker/pkg/label"
 	"github.com/dotcloud/docker/pkg/libcontainer"
+	"github.com/dotcloud/docker/pkg/libcontainer/mount"
 	"github.com/dotcloud/docker/pkg/system"
 	"os"
 	"path/filepath"
@@ -63,10 +64,10 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s
 			if err := system.Unshare(syscall.CLONE_NEWNS); err != nil {
 				return -1, err
 			}
-			if err := remountProc(); err != nil {
+			if err := mount.RemountProc(); err != nil {
 				return -1, fmt.Errorf("remount proc %s", err)
 			}
-			if err := remountSys(); err != nil {
+			if err := mount.RemountSys(); err != nil {
 				return -1, fmt.Errorf("remount sys %s", err)
 			}
 			goto dropAndExec

+ 10 - 27
pkg/libcontainer/nsinit/init.go

@@ -11,8 +11,10 @@ import (
 	"github.com/dotcloud/docker/pkg/apparmor"
 	"github.com/dotcloud/docker/pkg/label"
 	"github.com/dotcloud/docker/pkg/libcontainer"
-	"github.com/dotcloud/docker/pkg/libcontainer/capabilities"
+	"github.com/dotcloud/docker/pkg/libcontainer/console"
+	"github.com/dotcloud/docker/pkg/libcontainer/mount"
 	"github.com/dotcloud/docker/pkg/libcontainer/network"
+	"github.com/dotcloud/docker/pkg/libcontainer/security/capabilities"
 	"github.com/dotcloud/docker/pkg/libcontainer/utils"
 	"github.com/dotcloud/docker/pkg/system"
 	"github.com/dotcloud/docker/pkg/user"
@@ -20,7 +22,7 @@ import (
 
 // Init is the init process that first runs inside a new namespace to setup mounts, users, networking,
 // and other options required for the new container.
-func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, console string, syncPipe *SyncPipe, args []string) error {
+func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consolePath string, syncPipe *SyncPipe, args []string) error {
 	rootfs, err := utils.ResolveRootfs(uncleanRootfs)
 	if err != nil {
 		return err
@@ -36,20 +38,16 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol
 	ns.logger.Println("received context from parent")
 	syncPipe.Close()
 
-	if console != "" {
-		ns.logger.Printf("setting up %s as console\n", console)
-		slave, err := system.OpenTerminal(console, syscall.O_RDWR)
-		if err != nil {
-			return fmt.Errorf("open terminal %s", err)
-		}
-		if err := dupSlave(slave); err != nil {
-			return fmt.Errorf("dup2 slave %s", err)
+	if consolePath != "" {
+		ns.logger.Printf("setting up %s as console\n", consolePath)
+		if err := console.OpenAndDup(consolePath); err != nil {
+			return err
 		}
 	}
 	if _, err := system.Setsid(); err != nil {
 		return fmt.Errorf("setsid %s", err)
 	}
-	if console != "" {
+	if consolePath != "" {
 		if err := system.Setctty(); err != nil {
 			return fmt.Errorf("setctty %s", err)
 		}
@@ -60,7 +58,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol
 
 	label.Init()
 	ns.logger.Println("setup mount namespace")
-	if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot, container.Context["mount_label"]); err != nil {
+	if err := mount.InitializeMountNamespace(rootfs, consolePath, container); err != nil {
 		return fmt.Errorf("setup mount namespace %s", err)
 	}
 	if err := system.Sethostname(container.Hostname); err != nil {
@@ -114,21 +112,6 @@ func setupUser(container *libcontainer.Container) error {
 	return nil
 }
 
-// dupSlave dup2 the pty slave's fd into stdout and stdin and ensures that
-// the slave's fd is 0, or stdin
-func dupSlave(slave *os.File) error {
-	if err := system.Dup2(slave.Fd(), 0); err != nil {
-		return err
-	}
-	if err := system.Dup2(slave.Fd(), 1); err != nil {
-		return err
-	}
-	if err := system.Dup2(slave.Fd(), 2); err != nil {
-		return err
-	}
-	return nil
-}
-
 // setupVethNetwork uses the Network config if it is not nil to initialize
 // the new veth interface inside the container for use by changing the name to eth0
 // setting the MTU and IP address along with the default gateway

+ 0 - 265
pkg/libcontainer/nsinit/mount.go

@@ -1,265 +0,0 @@
-// +build linux
-
-package nsinit
-
-import (
-	"fmt"
-	"github.com/dotcloud/docker/pkg/label"
-	"github.com/dotcloud/docker/pkg/libcontainer"
-	"github.com/dotcloud/docker/pkg/system"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"syscall"
-)
-
-// default mount point flags
-const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
-
-// setupNewMountNamespace is used to initialize a new mount namespace for an new
-// container in the rootfs that is specified.
-//
-// There is no need to unmount the new mounts because as soon as the mount namespace
-// is no longer in use, the mounts will be removed automatically
-func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, console string, readonly, noPivotRoot bool, mountLabel string) error {
-	flag := syscall.MS_PRIVATE
-	if noPivotRoot {
-		flag = syscall.MS_SLAVE
-	}
-	if err := system.Mount("", "/", "", uintptr(flag|syscall.MS_REC), ""); err != nil {
-		return fmt.Errorf("mounting / as slave %s", err)
-	}
-	if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
-		return fmt.Errorf("mouting %s as bind %s", rootfs, err)
-	}
-	if err := mountSystem(rootfs, mountLabel); err != nil {
-		return fmt.Errorf("mount system %s", err)
-	}
-
-	for _, m := range bindMounts {
-		var (
-			flags = syscall.MS_BIND | syscall.MS_REC
-			dest  = filepath.Join(rootfs, m.Destination)
-		)
-		if !m.Writable {
-			flags = flags | syscall.MS_RDONLY
-		}
-		if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil {
-			return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err)
-		}
-		if !m.Writable {
-			if err := system.Mount(m.Source, dest, "bind", uintptr(flags|syscall.MS_REMOUNT), ""); err != nil {
-				return fmt.Errorf("remounting %s into %s %s", m.Source, dest, err)
-			}
-		}
-		if m.Private {
-			if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil {
-				return fmt.Errorf("mounting %s private %s", dest, err)
-			}
-		}
-	}
-
-	if err := copyDevNodes(rootfs); err != nil {
-		return fmt.Errorf("copy dev nodes %s", err)
-	}
-	if err := setupPtmx(rootfs, console, mountLabel); err != nil {
-		return err
-	}
-	if err := system.Chdir(rootfs); err != nil {
-		return fmt.Errorf("chdir into %s %s", rootfs, err)
-	}
-
-	if noPivotRoot {
-		if err := rootMsMove(rootfs); err != nil {
-			return err
-		}
-	} else {
-		if err := rootPivot(rootfs); err != nil {
-			return err
-		}
-	}
-
-	if readonly {
-		if err := system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil {
-			return fmt.Errorf("mounting %s as readonly %s", rootfs, err)
-		}
-	}
-
-	system.Umask(0022)
-
-	return nil
-}
-
-// use a pivot root to setup the rootfs
-func rootPivot(rootfs string) error {
-	pivotDir, err := ioutil.TempDir(rootfs, ".pivot_root")
-	if err != nil {
-		return fmt.Errorf("can't create pivot_root dir %s", pivotDir, err)
-	}
-	if err := system.Pivotroot(rootfs, pivotDir); err != nil {
-		return fmt.Errorf("pivot_root %s", err)
-	}
-	if err := system.Chdir("/"); err != nil {
-		return fmt.Errorf("chdir / %s", err)
-	}
-	// path to pivot dir now changed, update
-	pivotDir = filepath.Join("/", filepath.Base(pivotDir))
-	if err := system.Unmount(pivotDir, syscall.MNT_DETACH); err != nil {
-		return fmt.Errorf("unmount pivot_root dir %s", err)
-	}
-	if err := os.Remove(pivotDir); err != nil {
-		return fmt.Errorf("remove pivot_root dir %s", err)
-	}
-	return nil
-}
-
-// use MS_MOVE and chroot to setup the rootfs
-func rootMsMove(rootfs string) error {
-	if err := system.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil {
-		return fmt.Errorf("mount move %s into / %s", rootfs, err)
-	}
-	if err := system.Chroot("."); err != nil {
-		return fmt.Errorf("chroot . %s", err)
-	}
-	if err := system.Chdir("/"); err != nil {
-		return fmt.Errorf("chdir / %s", err)
-	}
-	return nil
-}
-
-// copyDevNodes mknods the hosts devices so the new container has access to them
-func copyDevNodes(rootfs string) error {
-	oldMask := system.Umask(0000)
-	defer system.Umask(oldMask)
-
-	for _, node := range []string{
-		"null",
-		"zero",
-		"full",
-		"random",
-		"urandom",
-		"tty",
-	} {
-		if err := copyDevNode(rootfs, node); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func copyDevNode(rootfs, node string) error {
-	stat, err := os.Stat(filepath.Join("/dev", node))
-	if err != nil {
-		return err
-	}
-	var (
-		dest = filepath.Join(rootfs, "dev", node)
-		st   = stat.Sys().(*syscall.Stat_t)
-	)
-	if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) {
-		return fmt.Errorf("copy %s %s", node, err)
-	}
-	return nil
-}
-
-// setupConsole ensures that the container has a proper /dev/console setup
-func setupConsole(rootfs, console string, mountLabel string) error {
-	oldMask := system.Umask(0000)
-	defer system.Umask(oldMask)
-
-	stat, err := os.Stat(console)
-	if err != nil {
-		return fmt.Errorf("stat console %s %s", console, err)
-	}
-	var (
-		st   = stat.Sys().(*syscall.Stat_t)
-		dest = filepath.Join(rootfs, "dev/console")
-	)
-	if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
-		return fmt.Errorf("remove %s %s", dest, err)
-	}
-	if err := os.Chmod(console, 0600); err != nil {
-		return err
-	}
-	if err := os.Chown(console, 0, 0); err != nil {
-		return err
-	}
-	if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil {
-		return fmt.Errorf("mknod %s %s", dest, err)
-	}
-	if err := label.SetFileLabel(console, mountLabel); err != nil {
-		return fmt.Errorf("SetFileLabel Failed %s %s", dest, err)
-	}
-	if err := system.Mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil {
-		return fmt.Errorf("bind %s to %s %s", console, dest, err)
-	}
-	return nil
-}
-
-// mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts
-// inside the mount namespace
-func mountSystem(rootfs string, mountLabel string) error {
-	for _, m := range []struct {
-		source string
-		path   string
-		device string
-		flags  int
-		data   string
-	}{
-		{source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags},
-		{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags},
-		{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)},
-		{source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)},
-	} {
-		if err := os.MkdirAll(m.path, 0755); err != nil && !os.IsExist(err) {
-			return fmt.Errorf("mkdirall %s %s", m.path, err)
-		}
-		if err := system.Mount(m.source, m.path, m.device, uintptr(m.flags), m.data); err != nil {
-			return fmt.Errorf("mounting %s into %s %s", m.source, m.path, err)
-		}
-	}
-	return nil
-}
-
-// setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and
-// finishes setting up /dev/console
-func setupPtmx(rootfs, console string, mountLabel string) error {
-	ptmx := filepath.Join(rootfs, "dev/ptmx")
-	if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) {
-		return err
-	}
-	if err := os.Symlink("pts/ptmx", ptmx); err != nil {
-		return fmt.Errorf("symlink dev ptmx %s", err)
-	}
-	if console != "" {
-		if err := setupConsole(rootfs, console, mountLabel); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// remountProc is used to detach and remount the proc filesystem
-// commonly needed with running a new process inside an existing container
-func remountProc() error {
-	if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil {
-		return err
-	}
-	if err := system.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), ""); err != nil {
-		return err
-	}
-	return nil
-}
-
-func remountSys() error {
-	if err := system.Unmount("/sys", syscall.MNT_DETACH); err != nil {
-		if err != syscall.EINVAL {
-			return err
-		}
-	} else {
-		if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaultMountFlags), ""); err != nil {
-			return err
-		}
-	}
-	return nil
-}

+ 0 - 0
pkg/libcontainer/capabilities/capabilities.go → pkg/libcontainer/security/capabilities/capabilities.go


+ 51 - 0
pkg/libcontainer/security/restrict/restrict.go

@@ -0,0 +1,51 @@
+package restrict
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"syscall"
+
+	"github.com/dotcloud/docker/pkg/system"
+)
+
+const flags = syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY
+
+var restrictions = map[string]string{
+	// dirs
+	"/proc/sys":  "",
+	"/proc/irq":  "",
+	"/proc/acpi": "",
+
+	// files
+	"/proc/sysrq-trigger": "/dev/null",
+	"/proc/kcore":         "/dev/null",
+}
+
+// Restrict locks down access to many areas of proc
+// by using the asumption that the user does not have mount caps to
+// revert the changes made here
+func Restrict(rootfs, empty string) error {
+	for dest, source := range restrictions {
+		dest = filepath.Join(rootfs, dest)
+
+		// we don't have a "/dev/null" for dirs so have the requester pass a dir
+		// for us to bind mount
+		switch source {
+		case "":
+			source = empty
+		default:
+			source = filepath.Join(rootfs, source)
+		}
+		if err := system.Mount(source, dest, "bind", flags, ""); err != nil {
+			if os.IsNotExist(err) {
+				continue
+			}
+			return fmt.Errorf("unable to mount %s over %s %s", source, dest, err)
+		}
+		if err := system.Mount("", dest, "bind", flags|syscall.MS_REMOUNT, ""); err != nil {
+			return fmt.Errorf("unable to mount %s over %s %s", source, dest, err)
+		}
+	}
+	return nil
+}

+ 20 - 0
pkg/libcontainer/types.go

@@ -11,6 +11,26 @@ var (
 	ErrUnsupported      = errors.New("Unsupported method")
 )
 
+type Mounts []Mount
+
+func (s Mounts) OfType(t string) Mounts {
+	out := Mounts{}
+	for _, m := range s {
+		if m.Type == t {
+			out = append(out, m)
+		}
+	}
+	return out
+}
+
+type Mount struct {
+	Type        string `json:"type,omitempty"`
+	Source      string `json:"source,omitempty"`      // Source path, in the host namespace
+	Destination string `json:"destination,omitempty"` // Destination path, in the container
+	Writable    bool   `json:"writable,omitempty"`
+	Private     bool   `json:"private,omitempty"`
+}
+
 // namespaceList is used to convert the libcontainer types
 // into the names of the files located in /proc/<pid>/ns/* for
 // each namespace

+ 25 - 8
registry/registry.go

@@ -292,6 +292,25 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
 	return nil, fmt.Errorf("Could not reach any registry endpoint")
 }
 
+func buildEndpointsList(headers []string, indexEp string) ([]string, error) {
+	var endpoints []string
+	parsedUrl, err := url.Parse(indexEp)
+	if err != nil {
+		return nil, err
+	}
+	var urlScheme = parsedUrl.Scheme
+	// The Registry's URL scheme has to match the Index'
+	for _, ep := range headers {
+		epList := strings.Split(ep, ",")
+		for _, epListElement := range epList {
+			endpoints = append(
+				endpoints,
+				fmt.Sprintf("%s://%s/v1/", urlScheme, strings.TrimSpace(epListElement)))
+		}
+	}
+	return endpoints, nil
+}
+
 func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
 	indexEp := r.indexEndpoint
 	repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote)
@@ -327,11 +346,10 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
 	}
 
 	var endpoints []string
-	var urlScheme = indexEp[:strings.Index(indexEp, ":")]
 	if res.Header.Get("X-Docker-Endpoints") != "" {
-		// The Registry's URL scheme has to match the Index'
-		for _, ep := range res.Header["X-Docker-Endpoints"] {
-			endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep))
+		endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], indexEp)
+		if err != nil {
+			return nil, err
 		}
 	} else {
 		return nil, fmt.Errorf("Index response didn't contain any endpoints")
@@ -560,7 +578,6 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
 	}
 
 	var tokens, endpoints []string
-	var urlScheme = indexEp[:strings.Index(indexEp, ":")]
 	if !validate {
 		if res.StatusCode != 200 && res.StatusCode != 201 {
 			errBody, err := ioutil.ReadAll(res.Body)
@@ -577,9 +594,9 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
 		}
 
 		if res.Header.Get("X-Docker-Endpoints") != "" {
-			// The Registry's URL scheme has to match the Index'
-			for _, ep := range res.Header["X-Docker-Endpoints"] {
-				endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep))
+			endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], indexEp)
+			if err != nil {
+				return nil, err
 			}
 		} else {
 			return nil, fmt.Errorf("Index response didn't contain any endpoints")

+ 1 - 1
registry/registry_mock_test.go

@@ -291,7 +291,7 @@ func handlerUsers(w http.ResponseWriter, r *http.Request) {
 
 func handlerImages(w http.ResponseWriter, r *http.Request) {
 	u, _ := url.Parse(testHttpServer.URL)
-	w.Header().Add("X-Docker-Endpoints", u.Host)
+	w.Header().Add("X-Docker-Endpoints", fmt.Sprintf("%s 	,  %s ", u.Host, "test.example.com"))
 	w.Header().Add("X-Docker-Token", fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano()))
 	if r.Method == "PUT" {
 		if strings.HasSuffix(r.URL.Path, "images") {

+ 14 - 1
registry/registry_test.go

@@ -1,7 +1,9 @@
 package registry
 
 import (
+	"fmt"
 	"github.com/dotcloud/docker/utils"
+	"net/url"
 	"strings"
 	"testing"
 )
@@ -99,12 +101,23 @@ func TestGetRemoteTags(t *testing.T) {
 
 func TestGetRepositoryData(t *testing.T) {
 	r := spawnTestRegistry(t)
+	parsedUrl, err := url.Parse(makeURL("/v1/"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	host := "http://" + parsedUrl.Host + "/v1/"
 	data, err := r.GetRepositoryData("foo42/bar")
 	if err != nil {
 		t.Fatal(err)
 	}
 	assertEqual(t, len(data.ImgList), 2, "Expected 2 images in ImgList")
-	assertEqual(t, len(data.Endpoints), 1, "Expected one endpoint in Endpoints")
+	assertEqual(t, len(data.Endpoints), 2,
+		fmt.Sprintf("Expected 2 endpoints in Endpoints, found %d instead", len(data.Endpoints)))
+	assertEqual(t, data.Endpoints[0], host,
+		fmt.Sprintf("Expected first endpoint to be %s but found %s instead", host, data.Endpoints[0]))
+	assertEqual(t, data.Endpoints[1], "http://test.example.com/v1/",
+		fmt.Sprintf("Expected first endpoint to be http://test.example.com/v1/ but found %s instead", data.Endpoints[1]))
+
 }
 
 func TestPushImageJSONRegistry(t *testing.T) {