瀏覽代碼

Merge branch 'master' into docker-osx

Guillaume J. Charmes 11 年之前
父節點
當前提交
f72e604872

+ 2 - 2
CHANGELOG.md

@@ -4,8 +4,8 @@
 
 
 #### Notable features since 0.6.0
 #### Notable features since 0.6.0
 
 
-* Storage drivers: choose from aufs, device mapper, vfs or btrfs.
-* Standard Linux support: docker now runs on unmodified linux kernels and all major distributions.
+* Storage drivers: choose from aufs, device-mapper, or vfs.
+* Standard Linux support: docker now runs on unmodified Linux kernels and all major distributions.
 * Links: compose complex software stacks by connecting containers to each other.
 * Links: compose complex software stacks by connecting containers to each other.
 * Container naming: organize your containers by giving them memorable names.
 * Container naming: organize your containers by giving them memorable names.
 * Advanced port redirects: specify port redirects per interface, or keep sensitive ports private.
 * Advanced port redirects: specify port redirects per interface, or keep sensitive ports private.

+ 14 - 1
api.go

@@ -15,6 +15,7 @@ import (
 	"mime"
 	"mime"
 	"net"
 	"net"
 	"net/http"
 	"net/http"
+	"net/http/pprof"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
 	"regexp"
 	"regexp"
@@ -1037,9 +1038,21 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s
 	}
 	}
 }
 }
 
 
+func AttachProfiler(router *mux.Router) {
+	router.HandleFunc("/debug/pprof/", pprof.Index)
+	router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
+	router.HandleFunc("/debug/pprof/profile", pprof.Profile)
+	router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
+	router.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP)
+	router.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)
+	router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
+}
+
 func createRouter(srv *Server, logging bool) (*mux.Router, error) {
 func createRouter(srv *Server, logging bool) (*mux.Router, error) {
 	r := mux.NewRouter()
 	r := mux.NewRouter()
-
+	if os.Getenv("DEBUG") != "" {
+		AttachProfiler(r)
+	}
 	m := map[string]map[string]HttpApiFunc{
 	m := map[string]map[string]HttpApiFunc{
 		"GET": {
 		"GET": {
 			"/events":                         getEvents,
 			"/events":                         getEvents,

+ 1 - 1
buildfile.go

@@ -288,7 +288,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
 		destPath = destPath + "/"
 		destPath = destPath + "/"
 	}
 	}
 	if !strings.HasPrefix(origPath, b.context) {
 	if !strings.HasPrefix(origPath, b.context) {
-		return fmt.Errorf("Forbidden path: %s", origPath)
+		return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
 	}
 	}
 	fi, err := os.Stat(origPath)
 	fi, err := os.Stat(origPath)
 	if err != nil {
 	if err != nil {

+ 7 - 3
commands.go

@@ -1666,8 +1666,11 @@ func (opts PathOpts) String() string { return fmt.Sprintf("%v", map[string]struc
 func (opts PathOpts) Set(val string) error {
 func (opts PathOpts) Set(val string) error {
 	var containerPath string
 	var containerPath string
 
 
-	splited := strings.SplitN(val, ":", 2)
-	if len(splited) == 1 {
+	if strings.Count(val, ":") > 2 {
+		return fmt.Errorf("bad format for volumes: %s", val)
+	}
+
+	if splited := strings.SplitN(val, ":", 2); len(splited) == 1 {
 		containerPath = splited[0]
 		containerPath = splited[0]
 		val = filepath.Clean(splited[0])
 		val = filepath.Clean(splited[0])
 	} else {
 	} else {
@@ -1680,6 +1683,7 @@ func (opts PathOpts) Set(val string) error {
 		return fmt.Errorf("%s is not an absolute path", containerPath)
 		return fmt.Errorf("%s is not an absolute path", containerPath)
 	}
 	}
 	opts[val] = struct{}{}
 	opts[val] = struct{}{}
+
 	return nil
 	return nil
 }
 }
 
 
@@ -2195,7 +2199,7 @@ func (cli *DockerCli) CmdCp(args ...string) error {
 }
 }
 
 
 func (cli *DockerCli) CmdSave(args ...string) error {
 func (cli *DockerCli) CmdSave(args ...string) error {
-	cmd := cli.Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive")
+	cmd := cli.Subcmd("save", "IMAGE", "Save an image to a tar archive (streamed to stdout)")
 	if err := cmd.Parse(args); err != nil {
 	if err := cmd.Parse(args); err != nil {
 		return err
 		return err
 	}
 	}

+ 157 - 0
commands_unit_test.go

@@ -0,0 +1,157 @@
+package docker
+
+import (
+	"strings"
+	"testing"
+)
+
+func parse(t *testing.T, args string) (*Config, *HostConfig, error) {
+	config, hostConfig, _, err := ParseRun(strings.Split(args+" ubuntu bash", " "), nil)
+	return config, hostConfig, err
+}
+
+func mustParse(t *testing.T, args string) (*Config, *HostConfig) {
+	config, hostConfig, err := parse(t, args)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return config, hostConfig
+}
+
+func TestParseRunLinks(t *testing.T) {
+	if _, hostConfig := mustParse(t, "-link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
+		t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
+	}
+	if _, hostConfig := mustParse(t, "-link a:b -link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
+		t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
+	}
+	if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
+		t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
+	}
+
+	if _, _, err := parse(t, "-link a"); err == nil {
+		t.Fatalf("Error parsing links. `-link a` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-link"); err == nil {
+		t.Fatalf("Error parsing links. `-link` should be an error but is not")
+	}
+}
+
+func TestParseRunAttach(t *testing.T) {
+	if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
+		t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
+	}
+	if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
+		t.Fatalf("Error parsing attach flags. Expect only Stdin and Stdout enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
+	}
+	if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
+		t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
+	}
+	if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
+		t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
+	}
+
+	if _, _, err := parse(t, "-a"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-a invalid"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-a invalid -a stdout"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-a stdin -d"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-a stdout -d"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-a stderr -d"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-d -rm"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-d -rm` should be an error but is not")
+	}
+}
+
+func TestParseRunVolumes(t *testing.T) {
+	if config, hostConfig := mustParse(t, "-v /tmp"); hostConfig.Binds != nil {
+		t.Fatalf("Error parsing volume flags, `-v /tmp` should not mount-bind anything. Received %v", hostConfig.Binds)
+	} else if _, exists := config.Volumes["/tmp"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
+	}
+
+	if config, hostConfig := mustParse(t, "-v /tmp -v /var"); hostConfig.Binds != nil {
+		t.Fatalf("Error parsing volume flags, `-v /tmp -v /var` should not mount-bind anything. Received %v", hostConfig.Binds)
+	} else if _, exists := config.Volumes["/tmp"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Recevied %v", config.Volumes)
+	} else if _, exists := config.Volumes["/var"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes)
+	}
+
+	if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
+		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
+	} else if _, exists := config.Volumes["/containerTmp"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
+	}
+
+	if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" || hostConfig.Binds[1] != "/hostVar:/containerVar" {
+		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /hostVar:/containerVar` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
+	} else if _, exists := config.Volumes["/containerTmp"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes)
+	} else if _, exists := config.Volumes["/containerVar"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
+	}
+
+	if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp:ro" || hostConfig.Binds[1] != "/hostVar:/containerVar:rw" {
+		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
+	} else if _, exists := config.Volumes["/containerTmp"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes)
+	} else if _, exists := config.Volumes["/containerVar"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
+	}
+
+	if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
+		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
+	} else if _, exists := config.Volumes["/containerTmp"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes)
+	} else if _, exists := config.Volumes["/containerVar"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
+	}
+
+	if config, hostConfig := mustParse(t, ""); hostConfig.Binds != nil {
+		t.Fatalf("Error parsing volume flags, without volume, nothing should be mount-binded. Received %v", hostConfig.Binds)
+	} else if len(config.Volumes) != 0 {
+		t.Fatalf("Error parsing volume flags, without volume, no volume should be present. Received %v", config.Volumes)
+	}
+
+	mustParse(t, "-v /")
+
+	if _, _, err := parse(t, "-v /:/"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v /:/` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v /tmp:"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v /tmp:ro"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v /tmp:ro` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v /tmp::"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v :"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v :` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v ::"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v ::` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v /tmp:/tmp:/tmp:/tmp"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v /tmp:/tmp:/tmp:/tmp` should fail but didn't")
+	}
+}

+ 31 - 0
contrib/vagrant-docker/README.md

@@ -17,3 +17,34 @@ meaning you can use Vagrant to control Docker containers.
 
 
 * [docker-provider](https://github.com/fgrehm/docker-provider)
 * [docker-provider](https://github.com/fgrehm/docker-provider)
 * [vagrant-shell](https://github.com/destructuring/vagrant-shell)
 * [vagrant-shell](https://github.com/destructuring/vagrant-shell)
+
+## Setting up Vagrant-docker with the Remote API
+
+The initial Docker upstart script will not work because it runs on `127.0.0.1`, which is not accessible to the host machine. Instead, we need to change the script to connect to `0.0.0.0`. To do this, modify `/etc/init/docker.conf` to look like this:
+
+```
+description     "Docker daemon"
+
+start on filesystem and started lxc-net
+stop on runlevel [!2345]
+
+respawn
+
+script
+    /usr/bin/docker -d -H=tcp://0.0.0.0:4243/
+end script
+```
+
+Once that's done, you need to set up a SSH tunnel between your host machine and the vagrant machine that's running Docker. This can be done by running the following command in a host terminal:
+
+```
+ssh -L 4243:localhost:4243 -p 2222 vagrant@localhost
+```
+
+(The first 4243 is what your host can connect to, the second 4243 is what port Docker is running on in the vagrant machine, and the 2222 is the port Vagrant is providing for SSH. If VirtualBox is the VM you're using, you can see what value "2222" should be by going to: Network > Adapter 1 > Advanced > Port Forwarding in the VirtualBox GUI.)
+
+Note that because the port has been changed, to run docker commands from within the command line you must run them like this:
+
+```
+sudo docker -H 0.0.0.0:4243 < commands for docker >
+```

+ 11 - 10
docs/sources/commandline/cli.rst

@@ -797,7 +797,7 @@ Known Issues (kill)
         -link="": Remove the link instead of the actual container
         -link="": Remove the link instead of the actual container
 
 
 Known Issues (rm)
 Known Issues (rm)
-~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~
 
 
 * :issue:`197` indicates that ``docker kill`` may leave directories
 * :issue:`197` indicates that ``docker kill`` may leave directories
   behind and make it difficult to remove the container.
   behind and make it difficult to remove the container.
@@ -881,8 +881,15 @@ containers will not be deleted.
       -name="": Assign the specified name to the container. If no name is specific docker will generate a random name
       -name="": Assign the specified name to the container. If no name is specific docker will generate a random name
       -P=false: Publish all exposed ports to the host interfaces
       -P=false: Publish all exposed ports to the host interfaces
 
 
-Examples
---------
+Known Issues (run -volumes-from)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* :issue:`2702`: "lxc-start: Permission denied - failed to mount"
+  could indicate a permissions problem with AppArmor. Please see the
+  issue for a workaround.
+
+Examples:
+~~~~~~~~~
 
 
 .. code-block:: bash
 .. code-block:: bash
 
 
@@ -974,16 +981,10 @@ id may be optionally suffixed with ``:ro`` or ``:rw`` to mount the volumes in
 read-only or read-write mode, respectively. By default, the volumes are mounted
 read-only or read-write mode, respectively. By default, the volumes are mounted
 in the same mode (rw or ro) as the reference container.
 in the same mode (rw or ro) as the reference container.
 
 
-Known Issues (run -volumes-from)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-* :issue:`2702`: "lxc-start: Permission denied - failed to mount"
-  could indicate a permissions problem with AppArmor. Please see the
-  issue for a workaround.
-
 .. _cli_save:
 .. _cli_save:
 
 
 ``save``
 ``save``
+---------
 
 
 ::
 ::
 
 

+ 0 - 2
docs/sources/examples/hello_world.rst

@@ -131,8 +131,6 @@ Attach to the container to see the results in real-time.
 
 
 - **"docker attach**" This will allow us to attach to a background
 - **"docker attach**" This will allow us to attach to a background
   process to see what is going on.
   process to see what is going on.
-- **"-sig-proxy=true"** Proxify all received signal to the process
-  (even in non-tty mode)
 - **$CONTAINER_ID** The Id of the container we want to attach too.
 - **$CONTAINER_ID** The Id of the container we want to attach too.
 
 
 Exit from the container attachment by pressing Control-C.
 Exit from the container attachment by pressing Control-C.

+ 16 - 14
docs/sources/examples/postgresql_service.rst

@@ -45,19 +45,21 @@ Install ``python-software-properties``.
     apt-get -y install python-software-properties
     apt-get -y install python-software-properties
     apt-get -y install software-properties-common
     apt-get -y install software-properties-common
 
 
-Add Pitti's PostgreSQL repository. It contains the most recent stable release
-of PostgreSQL i.e. ``9.2``.
+Add PostgreSQL's repository. It contains the most recent stable release
+of PostgreSQL i.e. ``9.3``.
 
 
 .. code-block:: bash
 .. code-block:: bash
 
 
-    add-apt-repository ppa:pitti/postgresql
+    apt-get -y install wget
+    wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
+    echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list
     apt-get update
     apt-get update
 
 
-Finally, install PostgreSQL 9.2
+Finally, install PostgreSQL 9.3
 
 
 .. code-block:: bash
 .. code-block:: bash
 
 
-    apt-get -y install postgresql-9.2 postgresql-client-9.2 postgresql-contrib-9.2
+    apt-get -y install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3
 
 
 Now, create a PostgreSQL superuser role that can create databases and
 Now, create a PostgreSQL superuser role that can create databases and
 other roles.  Following Vagrant's convention the role will be named
 other roles.  Following Vagrant's convention the role will be named
@@ -76,14 +78,14 @@ role.
 
 
 Adjust PostgreSQL configuration so that remote connections to the
 Adjust PostgreSQL configuration so that remote connections to the
 database are possible. Make sure that inside
 database are possible. Make sure that inside
-``/etc/postgresql/9.2/main/pg_hba.conf`` you have following line (you will need
+``/etc/postgresql/9.3/main/pg_hba.conf`` you have following line (you will need
 to install an editor, e.g. ``apt-get install vim``):
 to install an editor, e.g. ``apt-get install vim``):
 
 
 .. code-block:: bash
 .. code-block:: bash
 
 
     host    all             all             0.0.0.0/0               md5
     host    all             all             0.0.0.0/0               md5
 
 
-Additionaly, inside ``/etc/postgresql/9.2/main/postgresql.conf``
+Additionaly, inside ``/etc/postgresql/9.3/main/postgresql.conf``
 uncomment ``listen_addresses`` so it is as follows:
 uncomment ``listen_addresses`` so it is as follows:
 
 
 .. code-block:: bash
 .. code-block:: bash
@@ -115,9 +117,9 @@ Finally, run PostgreSQL server via ``docker``.
 
 
     CONTAINER=$(sudo docker run -d -p 5432 \
     CONTAINER=$(sudo docker run -d -p 5432 \
       -t <your username>/postgresql \
       -t <your username>/postgresql \
-      /bin/su postgres -c '/usr/lib/postgresql/9.2/bin/postgres \
-        -D /var/lib/postgresql/9.2/main \
-        -c config_file=/etc/postgresql/9.2/main/postgresql.conf')
+      /bin/su postgres -c '/usr/lib/postgresql/9.3/bin/postgres \
+        -D /var/lib/postgresql/9.3/main \
+        -c config_file=/etc/postgresql/9.3/main/postgresql.conf')
 
 
 Connect the PostgreSQL server using ``psql`` (You will need postgres installed
 Connect the PostgreSQL server using ``psql`` (You will need postgres installed
 on the machine.  For ubuntu, use something like
 on the machine.  For ubuntu, use something like
@@ -132,7 +134,7 @@ As before, create roles or databases if needed.
 
 
 .. code-block:: bash
 .. code-block:: bash
 
 
-    psql (9.2.4)
+    psql (9.3.1)
     Type "help" for help.
     Type "help" for help.
 
 
     docker=# CREATE DATABASE foo OWNER=docker;
     docker=# CREATE DATABASE foo OWNER=docker;
@@ -160,9 +162,9 @@ container starts.
 .. code-block:: bash
 .. code-block:: bash
 
 
     sudo docker commit -run='{"Cmd": \
     sudo docker commit -run='{"Cmd": \
-      ["/bin/su", "postgres", "-c", "/usr/lib/postgresql/9.2/bin/postgres -D \
-      /var/lib/postgresql/9.2/main -c \
-      config_file=/etc/postgresql/9.2/main/postgresql.conf"], "PortSpecs": ["5432"]}' \
+      ["/bin/su", "postgres", "-c", "/usr/lib/postgresql/9.3/bin/postgres -D \
+      /var/lib/postgresql/9.3/main -c \
+      config_file=/etc/postgresql/9.3/main/postgresql.conf"], "PortSpecs": ["5432"]}' \
       <container_id> <your username>/postgresql
       <container_id> <your username>/postgresql
 
 
 From now on, just type ``docker run <your username>/postgresql`` and
 From now on, just type ``docker run <your username>/postgresql`` and

+ 7 - 12
docs/sources/installation/archlinux.rst

@@ -12,27 +12,28 @@ Arch Linux
 .. include:: install_unofficial.inc
 .. include:: install_unofficial.inc
 
 
 Installing on Arch Linux is not officially supported but can be handled via 
 Installing on Arch Linux is not officially supported but can be handled via 
-either of the following AUR packages:
+one of the following AUR packages:
 
 
 * `lxc-docker <https://aur.archlinux.org/packages/lxc-docker/>`_
 * `lxc-docker <https://aur.archlinux.org/packages/lxc-docker/>`_
 * `lxc-docker-git <https://aur.archlinux.org/packages/lxc-docker-git/>`_
 * `lxc-docker-git <https://aur.archlinux.org/packages/lxc-docker-git/>`_
+* `lxc-docker-nightly <https://aur.archlinux.org/packages/lxc-docker-nightly/>`_
 
 
 The lxc-docker package will install the latest tagged version of docker. 
 The lxc-docker package will install the latest tagged version of docker. 
 The lxc-docker-git package will build from the current master branch.
 The lxc-docker-git package will build from the current master branch.
+The lxc-docker-nightly package will install the latest build.
 
 
 Dependencies
 Dependencies
 ------------
 ------------
 
 
 Docker depends on several packages which are specified as dependencies in
 Docker depends on several packages which are specified as dependencies in
-either AUR package.
+the AUR packages. The core dependencies are:
 
 
-* aufs3
 * bridge-utils
 * bridge-utils
-* go
+* device-mapper
 * iproute2
 * iproute2
-* linux-aufs_friendly
 * lxc
 * lxc
 
 
+
 Installation
 Installation
 ------------
 ------------
 
 
@@ -41,20 +42,14 @@ The instructions here assume **yaourt** is installed.  See
 for information on building and installing packages from the AUR if you have not
 for information on building and installing packages from the AUR if you have not
 done so before.
 done so before.
 
 
-Keep in mind that if **linux-aufs_friendly** is not already installed that a
-new kernel will be compiled and this can take quite a while.
-
 ::
 ::
 
 
-    yaourt -S lxc-docker-git
+    yaourt -S lxc-docker
 
 
 
 
 Starting Docker
 Starting Docker
 ---------------
 ---------------
 
 
-Prior to starting docker modify your bootloader to use the 
-**linux-aufs_friendly** kernel and reboot your system.
-
 There is a systemd service unit created for docker.  To start the docker service:
 There is a systemd service unit created for docker.  To start the docker service:
 
 
 ::
 ::

+ 2 - 2
graphdriver/devmapper/attachLoopback.go → graphdriver/devmapper/attach_loopback.go

@@ -46,7 +46,7 @@ func openNextAvailableLoopback(index int, sparseFile *osFile) (loopFile *osFile,
 			continue
 			continue
 		}
 		}
 
 
-		// Open the targeted loopback (use OpenFile because Open sets O_CLOEXEC)
+		// OpenFile adds O_CLOEXEC
 		loopFile, err = osOpenFile(target, osORdWr, 0644)
 		loopFile, err = osOpenFile(target, osORdWr, 0644)
 		if err != nil {
 		if err != nil {
 			utils.Errorf("Error openning loopback device: %s", err)
 			utils.Errorf("Error openning loopback device: %s", err)
@@ -91,7 +91,7 @@ func attachLoopDevice(sparseName string) (loop *osFile, err error) {
 		utils.Debugf("Error retrieving the next available loopback: %s", err)
 		utils.Debugf("Error retrieving the next available loopback: %s", err)
 	}
 	}
 
 
-	// Open the given sparse file (use OpenFile because Open sets O_CLOEXEC)
+	// OpenFile adds O_CLOEXEC
 	sparseFile, err := osOpenFile(sparseName, osORdWr, 0644)
 	sparseFile, err := osOpenFile(sparseName, osORdWr, 0644)
 	if err != nil {
 	if err != nil {
 		utils.Errorf("Error openning sparse file %s: %s", sparseName, err)
 		utils.Errorf("Error openning sparse file %s: %s", sparseName, err)

+ 5 - 2
graphdriver/devmapper/devmapper_wrapper.go

@@ -56,21 +56,24 @@ type (
 )
 )
 
 
 // FIXME: Make sure the values are defined in C
 // FIXME: Make sure the values are defined in C
+// IOCTL consts
 const (
 const (
+	BlkGetSize64 = C.BLKGETSIZE64
+
 	LoopSetFd       = C.LOOP_SET_FD
 	LoopSetFd       = C.LOOP_SET_FD
 	LoopCtlGetFree  = C.LOOP_CTL_GET_FREE
 	LoopCtlGetFree  = C.LOOP_CTL_GET_FREE
 	LoopGetStatus64 = C.LOOP_GET_STATUS64
 	LoopGetStatus64 = C.LOOP_GET_STATUS64
 	LoopSetStatus64 = C.LOOP_SET_STATUS64
 	LoopSetStatus64 = C.LOOP_SET_STATUS64
 	LoopClrFd       = C.LOOP_CLR_FD
 	LoopClrFd       = C.LOOP_CLR_FD
 	LoopSetCapacity = C.LOOP_SET_CAPACITY
 	LoopSetCapacity = C.LOOP_SET_CAPACITY
+)
 
 
+const (
 	LoFlagsAutoClear = C.LO_FLAGS_AUTOCLEAR
 	LoFlagsAutoClear = C.LO_FLAGS_AUTOCLEAR
 	LoFlagsReadOnly  = C.LO_FLAGS_READ_ONLY
 	LoFlagsReadOnly  = C.LO_FLAGS_READ_ONLY
 	LoFlagsPartScan  = C.LO_FLAGS_PARTSCAN
 	LoFlagsPartScan  = C.LO_FLAGS_PARTSCAN
 	LoKeySize        = C.LO_KEY_SIZE
 	LoKeySize        = C.LO_KEY_SIZE
 	LoNameSize       = C.LO_NAME_SIZE
 	LoNameSize       = C.LO_NAME_SIZE
-
-	BlkGetSize64 = C.BLKGETSIZE64
 )
 )
 
 
 var (
 var (

+ 64 - 60
graphdriver/devmapper/driver_test.go

@@ -57,12 +57,6 @@ func denyAllDevmapper() {
 	DmGetNextTarget = func(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
 	DmGetNextTarget = func(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
 		panic("DmGetNextTarget: this method should not be called here")
 		panic("DmGetNextTarget: this method should not be called here")
 	}
 	}
-	DmAttachLoopDevice = func(filename string, fd *int) string {
-		panic("DmAttachLoopDevice: this method should not be called here")
-	}
-	DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
-		panic("DmGetBlockSize: this method should not be called here")
-	}
 	DmUdevWait = func(cookie uint) int {
 	DmUdevWait = func(cookie uint) int {
 		panic("DmUdevWait: this method should not be called here")
 		panic("DmUdevWait: this method should not be called here")
 	}
 	}
@@ -78,9 +72,6 @@ func denyAllDevmapper() {
 	DmTaskDestroy = func(task *CDmTask) {
 	DmTaskDestroy = func(task *CDmTask) {
 		panic("DmTaskDestroy: this method should not be called here")
 		panic("DmTaskDestroy: this method should not be called here")
 	}
 	}
-	GetBlockSize = func(fd uintptr, size *uint64) sysErrno {
-		panic("GetBlockSize: this method should not be called here")
-	}
 	LogWithErrnoInit = func() {
 	LogWithErrnoInit = func() {
 		panic("LogWithErrnoInit: this method should not be called here")
 		panic("LogWithErrnoInit: this method should not be called here")
 	}
 	}
@@ -157,11 +148,10 @@ func (r Set) Assert(t *testing.T, names ...string) {
 
 
 func TestInit(t *testing.T) {
 func TestInit(t *testing.T) {
 	var (
 	var (
-		calls           = make(Set)
-		devicesAttached = make(Set)
-		taskMessages    = make(Set)
-		taskTypes       = make(Set)
-		home            = mkTestDirectory(t)
+		calls        = make(Set)
+		taskMessages = make(Set)
+		taskTypes    = make(Set)
+		home         = mkTestDirectory(t)
 	)
 	)
 	defer osRemoveAll(home)
 	defer osRemoveAll(home)
 
 
@@ -235,29 +225,6 @@ func TestInit(t *testing.T) {
 			taskMessages[message] = true
 			taskMessages[message] = true
 			return 1
 			return 1
 		}
 		}
-		var (
-			fakeDataLoop       = "/dev/loop42"
-			fakeMetadataLoop   = "/dev/loop43"
-			fakeDataLoopFd     = 42
-			fakeMetadataLoopFd = 43
-		)
-		var attachCount int
-		DmAttachLoopDevice = func(filename string, fd *int) string {
-			calls["DmAttachLoopDevice"] = true
-			if _, exists := devicesAttached[filename]; exists {
-				t.Fatalf("Already attached %s", filename)
-			}
-			devicesAttached[filename] = true
-			// This will crash if fd is not dereferenceable
-			if attachCount == 0 {
-				attachCount++
-				*fd = fakeDataLoopFd
-				return fakeDataLoop
-			} else {
-				*fd = fakeMetadataLoopFd
-				return fakeMetadataLoop
-			}
-		}
 		DmTaskDestroy = func(task *CDmTask) {
 		DmTaskDestroy = func(task *CDmTask) {
 			calls["DmTaskDestroy"] = true
 			calls["DmTaskDestroy"] = true
 			expectedTask := &task1
 			expectedTask := &task1
@@ -265,14 +232,6 @@ func TestInit(t *testing.T) {
 				t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
 				t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
 			}
 			}
 		}
 		}
-		fakeBlockSize := int64(4242 * 512)
-		DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
-			calls["DmGetBlockSize"] = true
-			if expectedFd := uintptr(42); fd != expectedFd {
-				t.Fatalf("Wrong libdevmapper call\nExpected: DmGetBlockSize(%v)\nReceived: DmGetBlockSize(%v)\n", expectedFd, fd)
-			}
-			return fakeBlockSize, 0
-		}
 		DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
 		DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
 			calls["DmTaskSetTarget"] = true
 			calls["DmTaskSetTarget"] = true
 			expectedTask := &task1
 			expectedTask := &task1
@@ -347,11 +306,9 @@ func TestInit(t *testing.T) {
 		"DmTaskSetName",
 		"DmTaskSetName",
 		"DmTaskRun",
 		"DmTaskRun",
 		"DmTaskGetInfo",
 		"DmTaskGetInfo",
-		"DmAttachLoopDevice",
 		"DmTaskDestroy",
 		"DmTaskDestroy",
 		"execRun",
 		"execRun",
 		"DmTaskCreate",
 		"DmTaskCreate",
-		"DmGetBlockSize",
 		"DmTaskSetTarget",
 		"DmTaskSetTarget",
 		"DmTaskSetCookie",
 		"DmTaskSetCookie",
 		"DmUdevWait",
 		"DmUdevWait",
@@ -359,7 +316,6 @@ func TestInit(t *testing.T) {
 		"DmTaskSetMessage",
 		"DmTaskSetMessage",
 		"DmTaskSetAddNode",
 		"DmTaskSetAddNode",
 	)
 	)
-	devicesAttached.Assert(t, path.Join(home, "devicemapper", "data"), path.Join(home, "devicemapper", "metadata"))
 	taskTypes.Assert(t, "0", "6", "17")
 	taskTypes.Assert(t, "0", "6", "17")
 	taskMessages.Assert(t, "create_thin 0", "set_transaction_id 0 1")
 	taskMessages.Assert(t, "create_thin 0", "set_transaction_id 0 1")
 }
 }
@@ -410,17 +366,9 @@ func mockAllDevmapper(calls Set) {
 		calls["DmTaskSetMessage"] = true
 		calls["DmTaskSetMessage"] = true
 		return 1
 		return 1
 	}
 	}
-	DmAttachLoopDevice = func(filename string, fd *int) string {
-		calls["DmAttachLoopDevice"] = true
-		return "/dev/loop42"
-	}
 	DmTaskDestroy = func(task *CDmTask) {
 	DmTaskDestroy = func(task *CDmTask) {
 		calls["DmTaskDestroy"] = true
 		calls["DmTaskDestroy"] = true
 	}
 	}
-	DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
-		calls["DmGetBlockSize"] = true
-		return int64(4242 * 512), 0
-	}
 	DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
 	DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
 		calls["DmTaskSetTarget"] = true
 		calls["DmTaskSetTarget"] = true
 		return 1
 		return 1
@@ -491,6 +439,32 @@ func TestDriverCreate(t *testing.T) {
 		return false, nil
 		return false, nil
 	}
 	}
 
 
+	sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
+		calls["sysSyscall"] = true
+		if trap != sysSysIoctl {
+			t.Fatalf("Unexpected syscall. Expecting SYS_IOCTL, received: %d", trap)
+		}
+		switch a2 {
+		case LoopSetFd:
+			calls["ioctl.loopsetfd"] = true
+		case LoopCtlGetFree:
+			calls["ioctl.loopctlgetfree"] = true
+		case LoopGetStatus64:
+			calls["ioctl.loopgetstatus"] = true
+		case LoopSetStatus64:
+			calls["ioctl.loopsetstatus"] = true
+		case LoopClrFd:
+			calls["ioctl.loopclrfd"] = true
+		case LoopSetCapacity:
+			calls["ioctl.loopsetcapacity"] = true
+		case BlkGetSize64:
+			calls["ioctl.blkgetsize"] = true
+		default:
+			t.Fatalf("Unexpected IOCTL. Received %d", a2)
+		}
+		return 0, 0, 0
+	}
+
 	func() {
 	func() {
 		d := newDriver(t)
 		d := newDriver(t)
 
 
@@ -500,16 +474,18 @@ func TestDriverCreate(t *testing.T) {
 			"DmTaskSetName",
 			"DmTaskSetName",
 			"DmTaskRun",
 			"DmTaskRun",
 			"DmTaskGetInfo",
 			"DmTaskGetInfo",
-			"DmAttachLoopDevice",
 			"execRun",
 			"execRun",
 			"DmTaskCreate",
 			"DmTaskCreate",
-			"DmGetBlockSize",
 			"DmTaskSetTarget",
 			"DmTaskSetTarget",
 			"DmTaskSetCookie",
 			"DmTaskSetCookie",
 			"DmUdevWait",
 			"DmUdevWait",
 			"DmTaskSetSector",
 			"DmTaskSetSector",
 			"DmTaskSetMessage",
 			"DmTaskSetMessage",
 			"DmTaskSetAddNode",
 			"DmTaskSetAddNode",
+			"sysSyscall",
+			"ioctl.blkgetsize",
+			"ioctl.loopsetfd",
+			"ioctl.loopsetstatus",
 		)
 		)
 
 
 		if err := d.Create("1", ""); err != nil {
 		if err := d.Create("1", ""); err != nil {
@@ -581,6 +557,32 @@ func TestDriverRemove(t *testing.T) {
 		return false, nil
 		return false, nil
 	}
 	}
 
 
+	sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
+		calls["sysSyscall"] = true
+		if trap != sysSysIoctl {
+			t.Fatalf("Unexpected syscall. Expecting SYS_IOCTL, received: %d", trap)
+		}
+		switch a2 {
+		case LoopSetFd:
+			calls["ioctl.loopsetfd"] = true
+		case LoopCtlGetFree:
+			calls["ioctl.loopctlgetfree"] = true
+		case LoopGetStatus64:
+			calls["ioctl.loopgetstatus"] = true
+		case LoopSetStatus64:
+			calls["ioctl.loopsetstatus"] = true
+		case LoopClrFd:
+			calls["ioctl.loopclrfd"] = true
+		case LoopSetCapacity:
+			calls["ioctl.loopsetcapacity"] = true
+		case BlkGetSize64:
+			calls["ioctl.blkgetsize"] = true
+		default:
+			t.Fatalf("Unexpected IOCTL. Received %d", a2)
+		}
+		return 0, 0, 0
+	}
+
 	func() {
 	func() {
 		d := newDriver(t)
 		d := newDriver(t)
 
 
@@ -590,16 +592,18 @@ func TestDriverRemove(t *testing.T) {
 			"DmTaskSetName",
 			"DmTaskSetName",
 			"DmTaskRun",
 			"DmTaskRun",
 			"DmTaskGetInfo",
 			"DmTaskGetInfo",
-			"DmAttachLoopDevice",
 			"execRun",
 			"execRun",
 			"DmTaskCreate",
 			"DmTaskCreate",
-			"DmGetBlockSize",
 			"DmTaskSetTarget",
 			"DmTaskSetTarget",
 			"DmTaskSetCookie",
 			"DmTaskSetCookie",
 			"DmUdevWait",
 			"DmUdevWait",
 			"DmTaskSetSector",
 			"DmTaskSetSector",
 			"DmTaskSetMessage",
 			"DmTaskSetMessage",
 			"DmTaskSetAddNode",
 			"DmTaskSetAddNode",
+			"sysSyscall",
+			"ioctl.blkgetsize",
+			"ioctl.loopsetfd",
+			"ioctl.loopsetstatus",
 		)
 		)
 
 
 		if err := d.Create("1", ""); err != nil {
 		if err := d.Create("1", ""); err != nil {

+ 1 - 3
graphdriver/devmapper/sys.go

@@ -36,9 +36,7 @@ var (
 	osRename     = os.Rename
 	osRename     = os.Rename
 	osReadlink   = os.Readlink
 	osReadlink   = os.Readlink
 
 
-	execRun = func(name string, args ...string) error {
-		return exec.Command(name, args...).Run()
-	}
+	execRun = func(name string, args ...string) error { return exec.Command(name, args...).Run() }
 )
 )
 
 
 const (
 const (

+ 28 - 11
hack/make/dyntest

@@ -19,18 +19,35 @@ fi
 bundle_test() {
 bundle_test() {
 	{
 	{
 		date
 		date
-		for test_dir in $(find_test_dirs); do (
-			set -x
-			cd $test_dir
+		
+		TESTS_FAILED=()
+		for test_dir in $(find_test_dirs); do
+			echo
 			
 			
-			# Install packages that are dependencies of the tests.
-			#   Note: Does not run the tests.
-			go test -i -ldflags "$LDFLAGS" $BUILDFLAGS
-			
-			# Run the tests with the optional $TESTFLAGS.
-			export TEST_DOCKERINIT_PATH=$DEST/../dynbinary/dockerinit-$VERSION
-			go test -v -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS $TESTFLAGS
-		)  done
+			if ! (
+				set -x
+				cd $test_dir
+				
+				# Install packages that are dependencies of the tests.
+				#   Note: Does not run the tests.
+				go test -i -ldflags "$LDFLAGS" $BUILDFLAGS
+				
+				# Run the tests with the optional $TESTFLAGS.
+				export TEST_DOCKERINIT_PATH=$DEST/../dynbinary/dockerinit-$VERSION
+				go test -v -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS $TESTFLAGS
+			); then
+				TESTS_FAILED+=("$test_dir")
+				sleep 1 # give it a second, so observers watching can take note
+			fi
+		done
+		
+		# if some tests fail, we want the bundlescript to fail, but we want to
+		# try running ALL the tests first, hence TESTS_FAILED
+		if [ "${#TESTS_FAILED[@]}" -gt 0 ]; then
+			echo
+			echo "Test failures in: ${TESTS_FAILED[@]}"
+			false
+		fi
 	} 2>&1 | tee $DEST/test.log
 	} 2>&1 | tee $DEST/test.log
 }
 }
 
 

+ 27 - 10
hack/make/test

@@ -13,17 +13,34 @@ set -e
 bundle_test() {
 bundle_test() {
 	{
 	{
 		date
 		date
-		for test_dir in $(find_test_dirs); do (
-			set -x
-			cd $test_dir
+		
+		TESTS_FAILED=()
+		for test_dir in $(find_test_dirs); do
+			echo
 			
 			
-			# Install packages that are dependencies of the tests.
-			#   Note: Does not run the tests.
-			go test -i -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS
-			
-			# Run the tests with the optional $TESTFLAGS.
-			go test -v -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS $TESTFLAGS
-		)  done
+			if ! (
+				set -x
+				cd $test_dir
+				
+				# Install packages that are dependencies of the tests.
+				#   Note: Does not run the tests.
+				go test -i -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS
+				
+				# Run the tests with the optional $TESTFLAGS.
+				go test -v -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS $TESTFLAGS
+			); then
+				TESTS_FAILED+=("$test_dir")
+				sleep 1 # give it a second, so observers watching can take note
+			fi
+		done
+		
+		# if some tests fail, we want the bundlescript to fail, but we want to
+		# try running ALL the tests first, hence TESTS_FAILED
+		if [ "${#TESTS_FAILED[@]}" -gt 0 ]; then
+			echo
+			echo "Test failures in: ${TESTS_FAILED[@]}"
+			false
+		fi
 	} 2>&1 | tee $DEST/test.log
 	} 2>&1 | tee $DEST/test.log
 }
 }
 
 

+ 1 - 1
integration/buildfile_test.go

@@ -483,7 +483,7 @@ func TestForbiddenContextPath(t *testing.T) {
 		t.Fail()
 		t.Fail()
 	}
 	}
 
 
-	if err.Error() != "Forbidden path: /" {
+	if err.Error() != "Forbidden path outside the build context: ../../ (/)" {
 		t.Logf("Error message is not expected: %s", err.Error())
 		t.Logf("Error message is not expected: %s", err.Error())
 		t.Fail()
 		t.Fail()
 	}
 	}

+ 23 - 15
lxc_template.go

@@ -1,6 +1,7 @@
 package docker
 package docker
 
 
 import (
 import (
+	"strings"
 	"text/template"
 	"text/template"
 )
 )
 
 
@@ -31,8 +32,8 @@ lxc.rootfs = {{$ROOTFS}}
 
 
 {{if and .HostnamePath .HostsPath}}
 {{if and .HostnamePath .HostsPath}}
 # enable domain name support
 # enable domain name support
-lxc.mount.entry = {{.HostnamePath}} {{$ROOTFS}}/etc/hostname none bind,ro 0 0
-lxc.mount.entry = {{.HostsPath}} {{$ROOTFS}}/etc/hosts none bind,ro 0 0
+lxc.mount.entry = {{escapeFstabSpaces .HostnamePath}} {{escapeFstabSpaces $ROOTFS}}/etc/hostname none bind,ro 0 0
+lxc.mount.entry = {{escapeFstabSpaces .HostsPath}} {{escapeFstabSpaces $ROOTFS}}/etc/hosts none bind,ro 0 0
 {{end}}
 {{end}}
 
 
 # use a dedicated pts for the container (and limit the number of pseudo terminal
 # use a dedicated pts for the container (and limit the number of pseudo terminal
@@ -84,27 +85,27 @@ lxc.cgroup.devices.allow = c 10:200 rwm
 lxc.pivotdir = lxc_putold
 lxc.pivotdir = lxc_putold
 #  WARNING: procfs is a known attack vector and should probably be disabled
 #  WARNING: procfs is a known attack vector and should probably be disabled
 #           if your userspace allows it. eg. see http://blog.zx2c4.com/749
 #           if your userspace allows it. eg. see http://blog.zx2c4.com/749
-lxc.mount.entry = proc {{$ROOTFS}}/proc proc nosuid,nodev,noexec 0 0
+lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noexec 0 0
 #  WARNING: sysfs is a known attack vector and should probably be disabled
 #  WARNING: sysfs is a known attack vector and should probably be disabled
 #           if your userspace allows it. eg. see http://bit.ly/T9CkqJ
 #           if your userspace allows it. eg. see http://bit.ly/T9CkqJ
-lxc.mount.entry = sysfs {{$ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0
-lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
-#lxc.mount.entry = varrun {{$ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0
-#lxc.mount.entry = varlock {{$ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0
-lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0
+lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0
+lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
+#lxc.mount.entry = varrun {{escapeFstabSpaces $ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0
+#lxc.mount.entry = varlock {{escapeFstabSpaces $ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0
+lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0
 
 
 # Inject dockerinit
 # Inject dockerinit
-lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/.dockerinit none bind,ro 0 0
+lxc.mount.entry = {{escapeFstabSpaces .SysInitPath}} {{escapeFstabSpaces $ROOTFS}}/.dockerinit none bind,ro 0 0
 
 
 # Inject env
 # Inject env
-lxc.mount.entry = {{.EnvConfigPath}} {{$ROOTFS}}/.dockerenv none bind,ro 0 0
+lxc.mount.entry = {{escapeFstabSpaces .EnvConfigPath}} {{escapeFstabSpaces $ROOTFS}}/.dockerenv none bind,ro 0 0
 
 
 # In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
 # In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
-lxc.mount.entry = {{.ResolvConfPath}} {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0
+lxc.mount.entry = {{escapeFstabSpaces .ResolvConfPath}} {{escapeFstabSpaces $ROOTFS}}/etc/resolv.conf none bind,ro 0 0
 {{if .Volumes}}
 {{if .Volumes}}
 {{ $rw := .VolumesRW }}
 {{ $rw := .VolumesRW }}
 {{range $virtualPath, $realPath := .Volumes}}
 {{range $virtualPath, $realPath := .Volumes}}
-lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,{{ if index $rw $virtualPath }}rw{{else}}ro{{end}} 0 0
+lxc.mount.entry = {{escapeFstabSpaces $realPath}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabSpaces $virtualPath}} none bind,{{ if index $rw $virtualPath }}rw{{else}}ro{{end}} 0 0
 {{end}}
 {{end}}
 {{end}}
 {{end}}
 
 
@@ -144,6 +145,12 @@ lxc.cgroup.cpu.shares = {{.Config.CpuShares}}
 
 
 var LxcTemplateCompiled *template.Template
 var LxcTemplateCompiled *template.Template
 
 
+// Escape spaces in strings according to the fstab documentation, which is the
+// format for "lxc.mount.entry" lines in lxc.conf. See also "man 5 fstab".
+func escapeFstabSpaces(field string) string {
+	return strings.Replace(field, " ", "\\040", -1)
+}
+
 func getMemorySwap(config *Config) int64 {
 func getMemorySwap(config *Config) int64 {
 	// By default, MemorySwap is set to twice the size of RAM.
 	// By default, MemorySwap is set to twice the size of RAM.
 	// If you want to omit MemorySwap, set it to `-1'.
 	// If you want to omit MemorySwap, set it to `-1'.
@@ -164,9 +171,10 @@ func getCapabilities(container *Container) *Capabilities {
 func init() {
 func init() {
 	var err error
 	var err error
 	funcMap := template.FuncMap{
 	funcMap := template.FuncMap{
-		"getMemorySwap":   getMemorySwap,
-		"getHostConfig":   getHostConfig,
-		"getCapabilities": getCapabilities,
+		"getMemorySwap":     getMemorySwap,
+		"getHostConfig":     getHostConfig,
+		"getCapabilities":   getCapabilities,
+		"escapeFstabSpaces": escapeFstabSpaces,
 	}
 	}
 	LxcTemplateCompiled, err = template.New("lxc").Funcs(funcMap).Parse(LxcTemplate)
 	LxcTemplateCompiled, err = template.New("lxc").Funcs(funcMap).Parse(LxcTemplate)
 	if err != nil {
 	if err != nil {

+ 18 - 0
lxc_template_unit_test.go

@@ -100,3 +100,21 @@ func grepFile(t *testing.T, path string, pattern string) {
 	}
 	}
 	t.Fatalf("grepFile: pattern \"%s\" not found in \"%s\"", pattern, path)
 	t.Fatalf("grepFile: pattern \"%s\" not found in \"%s\"", pattern, path)
 }
 }
+
+func TestEscapeFstabSpaces(t *testing.T) {
+	var testInputs = map[string]string{
+		" ":                      "\\040",
+		"":                       "",
+		"/double  space":         "/double\\040\\040space",
+		"/some long test string": "/some\\040long\\040test\\040string",
+		"/var/lib/docker":        "/var/lib/docker",
+		" leading":               "\\040leading",
+		"trailing ":              "trailing\\040",
+	}
+	for in, exp := range testInputs {
+		if out := escapeFstabSpaces(in); exp != out {
+			t.Logf("Expected %s got %s", exp, out)
+			t.Fail()
+		}
+	}
+}

+ 10 - 17
server.go

@@ -1011,16 +1011,9 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut
 		localName = remoteName
 		localName = remoteName
 	}
 	}
 
 
-	err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf, parallel)
-	if err == registry.ErrLoginRequired {
+	if err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf, parallel); err != nil {
 		return err
 		return err
 	}
 	}
-	if err != nil {
-		if err := srv.pullImage(r, out, remoteName, endpoint, nil, sf); err != nil {
-			return err
-		}
-		return nil
-	}
 
 
 	return nil
 	return nil
 }
 }
@@ -1417,19 +1410,15 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool)
 
 
 var ErrImageReferenced = errors.New("Image referenced by a repository")
 var ErrImageReferenced = errors.New("Image referenced by a repository")
 
 
-func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
+func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi, byParents map[string][]*Image) error {
 	// If the image is referenced by a repo, do not delete
 	// If the image is referenced by a repo, do not delete
 	if len(srv.runtime.repositories.ByID()[id]) != 0 {
 	if len(srv.runtime.repositories.ByID()[id]) != 0 {
 		return ErrImageReferenced
 		return ErrImageReferenced
 	}
 	}
 	// If the image is not referenced but has children, go recursive
 	// If the image is not referenced but has children, go recursive
 	referenced := false
 	referenced := false
-	byParents, err := srv.runtime.graph.ByParent()
-	if err != nil {
-		return err
-	}
 	for _, img := range byParents[id] {
 	for _, img := range byParents[id] {
-		if err := srv.deleteImageAndChildren(img.ID, imgs); err != nil {
+		if err := srv.deleteImageAndChildren(img.ID, imgs, byParents); err != nil {
 			if err != ErrImageReferenced {
 			if err != ErrImageReferenced {
 				return err
 				return err
 			}
 			}
@@ -1441,7 +1430,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
 	}
 	}
 
 
 	// If the image is not referenced and has no children, remove it
 	// If the image is not referenced and has no children, remove it
-	byParents, err = srv.runtime.graph.ByParent()
+	byParents, err := srv.runtime.graph.ByParent()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -1466,8 +1455,12 @@ func (srv *Server) deleteImageParents(img *Image, imgs *[]APIRmi) error {
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
+		byParents, err := srv.runtime.graph.ByParent()
+		if err != nil {
+			return err
+		}
 		// Remove all children images
 		// Remove all children images
-		if err := srv.deleteImageAndChildren(img.Parent, imgs); err != nil {
+		if err := srv.deleteImageAndChildren(img.Parent, imgs, byParents); err != nil {
 			return err
 			return err
 		}
 		}
 		return srv.deleteImageParents(parent, imgs)
 		return srv.deleteImageParents(parent, imgs)
@@ -1509,7 +1502,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro
 		}
 		}
 	}
 	}
 	if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
 	if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
-		if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil {
+		if err := srv.deleteImageAndChildren(img.ID, &imgs, nil); err != nil {
 			if err != ErrImageReferenced {
 			if err != ErrImageReferenced {
 				return imgs, err
 				return imgs, err
 			}
 			}