diff --git a/api/client.go b/api/client.go index 10075ae61..d7d126ec7 100644 --- a/api/client.go +++ b/api/client.go @@ -13,6 +13,7 @@ import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/nat" flag "github.com/dotcloud/docker/pkg/mflag" + "github.com/dotcloud/docker/pkg/signal" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" @@ -24,11 +25,11 @@ import ( "net/http/httputil" "net/url" "os" - "os/signal" + gosignal "os/signal" "path" "reflect" "regexp" - "runtime" + goruntime "runtime" "strconv" "strings" "syscall" @@ -367,7 +368,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error { if dockerversion.VERSION != "" { fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) } - fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version()) + fmt.Fprintf(cli.out, "Go version (client): %s\n", goruntime.Version()) if dockerversion.GITCOMMIT != "" { fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT) } @@ -432,7 +433,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers")) fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images")) - fmt.Fprintf(cli.out, "Driver: %s\n", remoteInfo.Get("Driver")) + fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver")) var driverStatus [][2]string if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil { return err @@ -440,14 +441,15 @@ func (cli *DockerCli) CmdInfo(args ...string) error { for _, pair := range driverStatus { fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) } + fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver")) + fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion")) + if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" { fmt.Fprintf(cli.out, "Debug mode (server): %v\n", remoteInfo.GetBool("Debug")) fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd")) fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines")) - fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver")) fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener")) - fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion")) if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" { fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1) @@ -533,13 +535,23 @@ func (cli *DockerCli) CmdRestart(args ...string) error { func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { sigc := make(chan os.Signal, 1) - utils.CatchAll(sigc) + signal.CatchAll(sigc) go func() { for s := range sigc { if s == syscall.SIGCHLD { continue } - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cid, s), nil, false)); err != nil { + var sig string + for sigStr, sigN := range signal.SignalMap { + if sigN == s { + sig = sigStr + break + } + } + if sig == "" { + utils.Errorf("Unsupported signal: %d. Discarding.", s) + } + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil { utils.Debugf("Error sending signal: %s", err) } } @@ -581,7 +593,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { if !container.Config.Tty { sigc := cli.forwardAllSignals(cmd.Arg(0)) - defer utils.StopCatch(sigc) + defer signal.StopCatch(sigc) } var in io.ReadCloser @@ -1614,7 +1626,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { if *proxy && !container.Config.Tty { sigc := cli.forwardAllSignals(cmd.Arg(0)) - defer utils.StopCatch(sigc) + defer signal.StopCatch(sigc) } if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil); err != nil { @@ -1753,7 +1765,21 @@ func (cli *DockerCli) CmdRun(args ...string) error { if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil { return fmt.Errorf("Failed to create the container ID file: %s", err) } - defer containerIDFile.Close() + defer func() { + containerIDFile.Close() + var ( + cidFileInfo os.FileInfo + err error + ) + if cidFileInfo, err = os.Stat(hostConfig.ContainerIDFile); err != nil { + return + } + if cidFileInfo.Size() == 0 { + if err := os.Remove(hostConfig.ContainerIDFile); err != nil { + fmt.Printf("failed to remove CID file '%s': %s \n", hostConfig.ContainerIDFile, err) + } + } + }() } containerValues := url.Values{} @@ -1818,7 +1844,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { if sigProxy { sigc := cli.forwardAllSignals(runResult.Get("Id")) - defer utils.StopCatch(sigc) + defer signal.StopCatch(sigc) } var ( @@ -2239,7 +2265,12 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea if setRawTerminal && cli.isTerminal { term.RestoreTerminal(cli.terminalFd, oldState) } - in.Close() + // For some reason this Close call blocks on darwin.. + // As the client exists right after, simply discard the close + // until we find a better solution. + if goruntime.GOOS != "darwin" { + in.Close() + } } }() @@ -2320,7 +2351,7 @@ func (cli *DockerCli) monitorTtySize(id string) error { cli.resizeTty(id) sigchan := make(chan os.Signal, 1) - signal.Notify(sigchan, syscall.SIGWINCH) + gosignal.Notify(sigchan, syscall.SIGWINCH) go func() { for _ = range sigchan { cli.resizeTty(id) diff --git a/api/server.go b/api/server.go index 6fafe60f9..771f3c6cd 100644 --- a/api/server.go +++ b/api/server.go @@ -26,7 +26,6 @@ import ( "strconv" "strings" "syscall" - "time" ) var ( @@ -883,7 +882,7 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp var copyData engine.Env - if contentType := r.Header.Get("Content-Type"); contentType == "application/json" { + if contentType := r.Header.Get("Content-Type"); MatchesContentType(contentType, "application/json") { if err := copyData.Decode(r.Body); err != nil { return err } @@ -1130,7 +1129,7 @@ func changeGroup(addr string, nameOrGid string) error { // ListenAndServe sets up the required http.Server and gets it listening for // each addr passed in and does protocol specific checking. -func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors bool, dockerVersion string, socketGroup string) error { +func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors bool, dockerVersion, socketGroup string) error { r, err := createRouter(eng, logging, enableCors, dockerVersion) if err != nil { @@ -1147,7 +1146,7 @@ func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors } } - l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock, 15*time.Minute) + l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock) if err != nil { return err } diff --git a/container.go b/container.go index 50332f27d..42b905a26 100644 --- a/container.go +++ b/container.go @@ -363,14 +363,18 @@ func populateCommand(c *Container) { driverConfig []string ) + en = &execdriver.Network{ + Mtu: c.runtime.config.Mtu, + Interface: nil, + } + if !c.Config.NetworkDisabled { network := c.NetworkSettings - en = &execdriver.Network{ + en.Interface = &execdriver.NetworkInterface{ Gateway: network.Gateway, Bridge: network.Bridge, IPAddress: network.IPAddress, IPPrefixLen: network.IPPrefixLen, - Mtu: c.runtime.config.Mtu, } } @@ -784,7 +788,7 @@ func (container *Container) monitor(callback execdriver.StartCallback) error { utils.Errorf("Error running container: %s", err) } - if container.runtime.srv.IsRunning() { + if container.runtime != nil && container.runtime.srv != nil && container.runtime.srv.IsRunning() { container.State.SetStopped(exitCode) // FIXME: there is a race condition here which causes this to fail during the unit tests. diff --git a/contrib/init/sysvinit-debian/docker b/contrib/init/sysvinit-debian/docker index 510683a45..67f0d2807 100755 --- a/contrib/init/sysvinit-debian/docker +++ b/contrib/init/sysvinit-debian/docker @@ -21,6 +21,7 @@ BASE=$(basename $0) # modify these in /etc/default/$BASE (/etc/default/docker) DOCKER=/usr/bin/$BASE DOCKER_PIDFILE=/var/run/$BASE.pid +DOCKER_LOGFILE=/var/log/$BASE.log DOCKER_OPTS= DOCKER_DESC="Docker" @@ -50,23 +51,37 @@ fail_unless_root() { fi } +cgroupfs_mount() { + # see also https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount + if grep -v '^#' /etc/fstab | grep -q cgroup \ + || [ ! -e /proc/cgroups ] \ + || [ ! -d /sys/fs/cgroup ]; then + return + fi + if ! mountpoint -q /sys/fs/cgroup; then + mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup + fi + ( + cd /sys/fs/cgroup + for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do + mkdir -p $sys + if ! mountpoint -q $sys; then + if ! mount -n -t cgroup -o $sys cgroup $sys; then + rmdir $sys || true + fi + fi + done + ) +} + case "$1" in start) fail_unless_root - if ! grep -q cgroup /proc/mounts; then - # rough approximation of cgroupfs-mount - mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup - for sys in $(cut -d' ' -f1 /proc/cgroups); do - mkdir -p /sys/fs/cgroup/$sys - if ! mount -n -t cgroup -o $sys cgroup /sys/fs/cgroup/$sys 2>/dev/null; then - rmdir /sys/fs/cgroup/$sys 2>/dev/null || true - fi - done - fi + cgroupfs_mount - touch /var/log/docker.log - chgrp docker /var/log/docker.log + touch "$DOCKER_LOGFILE" + chgrp docker "$DOCKER_LOGFILE" log_begin_msg "Starting $DOCKER_DESC: $BASE" start-stop-daemon --start --background \ @@ -76,7 +91,7 @@ case "$1" in -- \ -d -p "$DOCKER_PIDFILE" \ $DOCKER_OPTS \ - > /var/log/docker.log 2>&1 + >> "$DOCKER_LOGFILE" 2>&1 log_end_msg $? ;; diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf index e2cc4536e..047f21c09 100644 --- a/contrib/init/upstart/docker.conf +++ b/contrib/init/upstart/docker.conf @@ -5,6 +5,29 @@ stop on runlevel [!2345] respawn +pre-start script + # see also https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount + if grep -v '^#' /etc/fstab | grep -q cgroup \ + || [ ! -e /proc/cgroups ] \ + || [ ! -d /sys/fs/cgroup ]; then + exit 0 + fi + if ! mountpoint -q /sys/fs/cgroup; then + mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup + fi + ( + cd /sys/fs/cgroup + for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do + mkdir -p $sys + if ! mountpoint -q $sys; then + if ! mount -n -t cgroup -o $sys cgroup $sys; then + rmdir $sys || true + fi + fi + done + ) +end script + script # modify these in /etc/default/$UPSTART_JOB (/etc/default/docker) DOCKER=/usr/bin/$UPSTART_JOB @@ -12,15 +35,5 @@ script if [ -f /etc/default/$UPSTART_JOB ]; then . /etc/default/$UPSTART_JOB fi - if ! grep -q cgroup /proc/mounts; then - # rough approximation of cgroupfs-mount - mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup - for sys in $(cut -d' ' -f1 /proc/cgroups); do - mkdir -p /sys/fs/cgroup/$sys - if ! mount -n -t cgroup -o $sys cgroup /sys/fs/cgroup/$sys 2>/dev/null; then - rmdir /sys/fs/cgroup/$sys 2>/dev/null || true - fi - done - fi "$DOCKER" -d $DOCKER_OPTS end script diff --git a/docs/sources/articles/security.rst b/docs/sources/articles/security.rst index 3dc5780e8..e738e9a84 100644 --- a/docs/sources/articles/security.rst +++ b/docs/sources/articles/security.rst @@ -7,7 +7,7 @@ Docker Security =============== - *Adapted from* `Containers & Docker: How Secure are They? `_ + *Adapted from* `Containers & Docker: How Secure are They? `_ There are three major areas to consider when reviewing Docker security: @@ -261,7 +261,7 @@ with Docker, since everything is provided by the kernel anyway. For more context and especially for comparisons with VMs and other container systems, please also see the `original blog post -`_. +`_. .. _blogsecurity: http://blog.docker.io/2013/08/containers-docker-how-secure-are-they/ diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 2e49cd5ca..9e99671ba 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -88,7 +88,7 @@ Commands -v, --version=false: Print version information and quit --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available -The Docker daemon is the persistent process that manages containers. Docker uses the same binary for both the +The Docker daemon is the persistent process that manages containers. Docker uses the same binary for both the daemon and client. To run the daemon you provide the ``-d`` flag. To force Docker to use devicemapper as the storage driver, use ``docker -d -s devicemapper``. @@ -100,10 +100,10 @@ To run the daemon with debug output, use ``docker -d -D``. To use lxc as the execution driver, use ``docker -d -e lxc``. The docker client will also honor the ``DOCKER_HOST`` environment variable to set -the ``-H`` flag for the client. +the ``-H`` flag for the client. :: - + docker -H tcp://0.0.0.0:4243 ps # or export DOCKER_HOST="tcp://0.0.0.0:4243" @@ -141,7 +141,7 @@ TMPDIR and the data directory can be set like this: You can detach from the container again (and leave it running) with ``CTRL-c`` (for a quiet exit) or ``CTRL-\`` to get a stacktrace of -the Docker client when it quits. When you detach from the container's +the Docker client when it quits. When you detach from the container's process the exit code will be returned to the client. To stop a container, use ``docker stop``. @@ -303,7 +303,7 @@ by using the ``git://`` schema. -m, --message="": Commit message -a, --author="": Author (eg. "John Hannibal Smith " - --run="": Configuration to be applied when the image is launched with `docker run`. + --run="": Configuration changes to be applied when the image is launched with `docker run`. (ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}') .. _cli_commit_examples: @@ -315,14 +315,14 @@ Commit an existing container $ sudo docker ps ID IMAGE COMMAND CREATED STATUS PORTS - c3f279d17e0a ubuntu:12.04 /bin/bash 7 days ago Up 25 hours - 197387f1b436 ubuntu:12.04 /bin/bash 7 days ago Up 25 hours + c3f279d17e0a ubuntu:12.04 /bin/bash 7 days ago Up 25 hours + 197387f1b436 ubuntu:12.04 /bin/bash 7 days ago Up 25 hours $ docker commit c3f279d17e0a SvenDowideit/testimage:version3 f5283438590d $ docker images | head REPOSITORY TAG ID CREATED VIRTUAL SIZE SvenDowideit/testimage version3 f5283438590d 16 seconds ago 335.7 MB - + Change the command that a container runs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -344,11 +344,40 @@ run ``ls /etc``. apt host.conf lsb-base rc2.d ... +Merged configs example +...................... + +Say you have a Dockerfile like so: + +.. code-block:: bash + + ENV MYVAR foobar + RUN apt-get install openssh + EXPOSE 22 + CMD ["/usr/sbin/sshd -D"] + ... + +If you run that, make some changes, and then commit, Docker will merge the environment variable and exposed port configuration settings with any that you specify in the -run= option. This is a change from Docker 0.8.0 and prior where no attempt was made to preserve any existing configuration on commit. + +.. code-block:: bash + + $ docker build -t me/foo . + $ docker run -t -i me/foo /bin/bash + foo-container$ [make changes in the container] + foo-container$ exit + $ docker commit -run='{"Cmd": ["ls"]}' [container-id] me/bar + ... + +The me/bar image will now have port 22 exposed, MYVAR env var set to 'foobar', and its default command will be ["ls"]. + +Note that this is currently a shallow merge. So, for example, if you had specified a new port spec in the -run= config above, that would have clobbered the 'EXPOSE 22' setting from the parent container. + Full -run example ................. The ``--run`` JSON hash changes the ``Config`` section when running ``docker inspect CONTAINERID`` -or ``config`` when running ``docker inspect IMAGEID``. +or ``config`` when running ``docker inspect IMAGEID``. Existing configuration key-values that are +not overridden in the JSON hash will be merged in. (Multiline is okay within a single quote ``'``) @@ -664,7 +693,7 @@ Displaying image hierarchy Usage: docker import URL|- [REPOSITORY[:TAG]] - Create an empty filesystem image and import the contents of the tarball + Create an empty filesystem image and import the contents of the tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then optionally tag it. At this time, the URL must start with ``http`` and point to a single @@ -942,7 +971,7 @@ Running ``docker ps`` showing 2 linked containers. $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - 4c01db0b339c ubuntu:12.04 bash 17 seconds ago Up 16 seconds webapp + 4c01db0b339c ubuntu:12.04 bash 17 seconds ago Up 16 seconds webapp d7886598dbe2 crosbymichael/redis:latest /redis-server --dir 33 minutes ago Up 33 minutes 6379/tcp redis,webapp/db fd2645e2e2b5 busybox:latest top 10 days ago Ghost insane_ptolemy @@ -1047,7 +1076,7 @@ containers will not be deleted. Remove one or more images -f, --force=false: Force - + Removing tagged images ~~~~~~~~~~~~~~~~~~~~~~ @@ -1126,8 +1155,8 @@ Once the container is stopped it still exists and can be started back up. See ` The ``docker run`` command can be used in combination with ``docker commit`` to :ref:`change the command that a container runs `. -See :ref:`port_redirection` for more detailed information about the ``--expose``, -``-p``, ``-P`` and ``--link`` parameters, and :ref:`working_with_links_names` for +See :ref:`port_redirection` for more detailed information about the ``--expose``, +``-p``, ``-P`` and ``--link`` parameters, and :ref:`working_with_links_names` for specific examples using ``--link``. Known Issues (run -volumes-from) @@ -1207,8 +1236,8 @@ starting your container. $ sudo docker run -t -i -v /var/run/docker.sock:/var/run/docker.sock -v ./static-docker:/usr/bin/docker busybox sh -By bind-mounting the docker unix socket and statically linked docker binary -(such as that provided by https://get.docker.io), you give the container +By bind-mounting the docker unix socket and statically linked docker binary +(such as that provided by https://get.docker.io), you give the container the full access to create and manipulate the host's docker daemon. .. code-block:: bash diff --git a/execdriver/driver.go b/execdriver/driver.go index ec8f48f52..bb7aad272 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -84,11 +84,15 @@ type Driver interface { // Network settings of the container type Network struct { + Interface *NetworkInterface `json:"interface"` // if interface is nil then networking is disabled + Mtu int `json:"mtu"` +} + +type NetworkInterface struct { Gateway string `json:"gateway"` IPAddress string `json:"ip"` Bridge string `json:"bridge"` IPPrefixLen int `json:"ip_prefix_len"` - Mtu int `json:"mtu"` } type Resources struct { @@ -111,8 +115,8 @@ type Command struct { WorkingDir string `json:"working_dir"` ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver Tty bool `json:"tty"` - Network *Network `json:"network"` // if network is nil then networking is disabled - Config []string `json:"config"` // generic values that specific drivers can consume + Network *Network `json:"network"` + Config []string `json:"config"` // generic values that specific drivers can consume Resources *Resources `json:"resources"` Terminal Terminal `json:"-"` // standard or tty terminal diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 765a52ee4..f24b3b51b 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -94,13 +94,15 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba DriverName, } - if c.Network != nil { + if c.Network.Interface != nil { params = append(params, - "-g", c.Network.Gateway, - "-i", fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen), - "-mtu", strconv.Itoa(c.Network.Mtu), + "-g", c.Network.Interface.Gateway, + "-i", fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen), ) } + params = append(params, + "-mtu", strconv.Itoa(c.Network.Mtu), + ) if c.User != "" { params = append(params, "-u", c.User) @@ -168,6 +170,9 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba // Poll lxc for RUNNING status pid, err := d.waitForStart(c, waitLock) if err != nil { + if c.Process != nil { + c.Process.Kill() + } return -1, err } c.ContainerPid = pid diff --git a/execdriver/lxc/info.go b/execdriver/lxc/info.go index 3b2ea0d07..27b4c5860 100644 --- a/execdriver/lxc/info.go +++ b/execdriver/lxc/info.go @@ -36,7 +36,7 @@ func parseLxcInfo(raw string) (*lxcInfo, error) { if len(parts) < 2 { continue } - switch strings.TrimSpace(parts[0]) { + switch strings.ToLower(strings.TrimSpace(parts[0])) { case "state": info.Running = strings.TrimSpace(parts[1]) == "RUNNING" case "pid": diff --git a/execdriver/lxc/lxc_template.go b/execdriver/lxc/lxc_template.go index 1181396a1..03d01e893 100644 --- a/execdriver/lxc/lxc_template.go +++ b/execdriver/lxc/lxc_template.go @@ -7,17 +7,17 @@ import ( ) const LxcTemplate = ` -{{if .Network}} +{{if .Network.Interface}} # network configuration lxc.network.type = veth -lxc.network.link = {{.Network.Bridge}} +lxc.network.link = {{.Network.Interface.Bridge}} lxc.network.name = eth0 -lxc.network.mtu = {{.Network.Mtu}} {{else}} # network is disabled (-n=false) lxc.network.type = empty lxc.network.flags = up {{end}} +lxc.network.mtu = {{.Network.Mtu}} # root filesystem {{$ROOTFS := .Rootfs}} diff --git a/execdriver/lxc/lxc_template_unit_test.go b/execdriver/lxc/lxc_template_unit_test.go index 99d6e636f..6fea86d29 100644 --- a/execdriver/lxc/lxc_template_unit_test.go +++ b/execdriver/lxc/lxc_template_unit_test.go @@ -43,6 +43,10 @@ func TestLXCConfig(t *testing.T) { Memory: int64(mem), CpuShares: int64(cpu), }, + Network: &execdriver.Network{ + Mtu: 1500, + Interface: nil, + }, } p, err := driver.generateLXCConfig(command) if err != nil { @@ -75,6 +79,10 @@ func TestCustomLxcConfig(t *testing.T) { "lxc.utsname = docker", "lxc.cgroup.cpuset.cpus = 0,1", }, + Network: &execdriver.Network{ + Mtu: 1500, + Interface: nil, + }, } p, err := driver.generateLXCConfig(command) diff --git a/execdriver/native/default_template.go b/execdriver/native/default_template.go index 6e7d597b7..62d804016 100644 --- a/execdriver/native/default_template.go +++ b/execdriver/native/default_template.go @@ -19,19 +19,30 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.WorkingDir = c.WorkingDir container.Env = c.Env - if c.Network != nil { - container.Networks = []*libcontainer.Network{ - { - Mtu: c.Network.Mtu, - Address: fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen), - Gateway: c.Network.Gateway, - Type: "veth", - Context: libcontainer.Context{ - "prefix": "veth", - "bridge": c.Network.Bridge, - }, + loopbackNetwork := libcontainer.Network{ + Mtu: c.Network.Mtu, + Address: fmt.Sprintf("%s/%d", "127.0.0.1", 0), + Gateway: "localhost", + Type: "loopback", + Context: libcontainer.Context{}, + } + + container.Networks = []*libcontainer.Network{ + &loopbackNetwork, + } + + if c.Network.Interface != nil { + vethNetwork := libcontainer.Network{ + Mtu: c.Network.Mtu, + Address: fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen), + Gateway: c.Network.Interface.Gateway, + Type: "veth", + Context: libcontainer.Context{ + "prefix": "veth", + "bridge": c.Network.Interface.Bridge, }, } + container.Networks = append(container.Networks, &vethNetwork) } container.Cgroups.Name = c.ID diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index 303e363e9..4d33e243e 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -39,6 +39,13 @@ type DevInfo struct { // first get (since we need to mount to set up the device // a bit first). floating bool `json:"-"` + + // The global DeviceSet lock guarantees that we serialize all + // the calls to libdevmapper (which is not threadsafe), but we + // sometimes release that lock while sleeping. In that case + // this per-device lock is still held, protecting against + // other accesses to the device that we're doing the wait on. + lock sync.Mutex `json:"-"` } type MetaData struct { @@ -47,7 +54,7 @@ type MetaData struct { type DeviceSet struct { MetaData - sync.Mutex + sync.Mutex // Protects Devices map and serializes calls into libdevmapper root string devicePrefix string TransactionId uint64 @@ -569,6 +576,9 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error { return fmt.Errorf("Error adding device for '%s': can't find device for parent '%s'", hash, baseHash) } + baseInfo.lock.Lock() + defer baseInfo.lock.Unlock() + deviceId := devices.allocateDeviceId() if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil { @@ -636,6 +646,14 @@ func (devices *DeviceSet) DeleteDevice(hash string) error { devices.Lock() defer devices.Unlock() + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("Unknown device %s", hash) + } + + info.lock.Lock() + defer info.lock.Unlock() + return devices.deleteDevice(hash) } @@ -683,7 +701,7 @@ func (devices *DeviceSet) deactivateDevice(hash string) error { func (devices *DeviceSet) removeDeviceAndWait(devname string) error { var err error - for i := 0; i < 10; i++ { + for i := 0; i < 1000; i++ { devices.sawBusy = false err = removeDevice(devname) if err == nil { @@ -695,7 +713,9 @@ func (devices *DeviceSet) removeDeviceAndWait(devname string) error { // If we see EBUSY it may be a transient error, // sleep a bit a retry a few times. - time.Sleep(5 * time.Millisecond) + devices.Unlock() + time.Sleep(10 * time.Millisecond) + devices.Lock() } if err != nil { return err @@ -709,7 +729,7 @@ func (devices *DeviceSet) removeDeviceAndWait(devname string) error { // waitRemove blocks until either: // a) the device registered at - is removed, -// or b) the 1 second timeout expires. +// or b) the 10 second timeout expires. func (devices *DeviceSet) waitRemove(devname string) error { utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, devname) defer utils.Debugf("[deviceset %s] waitRemove(%s) END", devices.devicePrefix, devname) @@ -728,7 +748,9 @@ func (devices *DeviceSet) waitRemove(devname string) error { break } - time.Sleep(1 * time.Millisecond) + devices.Unlock() + time.Sleep(10 * time.Millisecond) + devices.Lock() } if i == 1000 { return fmt.Errorf("Timeout while waiting for device %s to be removed", devname) @@ -738,7 +760,7 @@ func (devices *DeviceSet) waitRemove(devname string) error { // waitClose blocks until either: // a) the device registered at - is closed, -// or b) the 1 second timeout expires. +// or b) the 10 second timeout expires. func (devices *DeviceSet) waitClose(hash string) error { info := devices.Devices[hash] if info == nil { @@ -756,7 +778,9 @@ func (devices *DeviceSet) waitClose(hash string) error { if devinfo.OpenCount == 0 { break } - time.Sleep(1 * time.Millisecond) + devices.Unlock() + time.Sleep(10 * time.Millisecond) + devices.Lock() } if i == 1000 { return fmt.Errorf("Timeout while waiting for device %s to close", hash) @@ -773,20 +797,26 @@ func (devices *DeviceSet) Shutdown() error { defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix) for _, info := range devices.Devices { + info.lock.Lock() if info.mountCount > 0 { if err := sysUnmount(info.mountPath, 0); err != nil { utils.Debugf("Shutdown unmounting %s, error: %s\n", info.mountPath, err) } } + info.lock.Unlock() } for _, d := range devices.Devices { + d.lock.Lock() + if err := devices.waitClose(d.Hash); err != nil { utils.Errorf("Warning: error waiting for device %s to unmount: %s\n", d.Hash, err) } if err := devices.deactivateDevice(d.Hash); err != nil { utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err) } + + d.lock.Unlock() } if err := devices.deactivatePool(); err != nil { @@ -805,6 +835,9 @@ func (devices *DeviceSet) MountDevice(hash, path string) error { return fmt.Errorf("Unknown device %s", hash) } + info.lock.Lock() + defer info.lock.Unlock() + if info.mountCount > 0 { if path != info.mountPath { return fmt.Errorf("Trying to mount devmapper device in multple places (%s, %s)", info.mountPath, path) @@ -851,6 +884,9 @@ func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error { return fmt.Errorf("UnmountDevice: no such device %s\n", hash) } + info.lock.Lock() + defer info.lock.Unlock() + if mode == UnmountFloat { if info.floating { return fmt.Errorf("UnmountDevice: can't float floating reference %s\n", hash) @@ -920,6 +956,10 @@ func (devices *DeviceSet) HasActivatedDevice(hash string) bool { if info == nil { return false } + + info.lock.Lock() + defer info.lock.Unlock() + devinfo, _ := getInfo(info.Name()) return devinfo != nil && devinfo.Exists != 0 } @@ -974,6 +1014,9 @@ func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) { return nil, fmt.Errorf("No device %s", hash) } + info.lock.Lock() + defer info.lock.Unlock() + status := &DevStatus{ DeviceId: info.DeviceId, Size: info.Size, diff --git a/graphdriver/devmapper/driver.go b/graphdriver/devmapper/driver.go index 4d414f9a7..8c5a19eea 100644 --- a/graphdriver/devmapper/driver.go +++ b/graphdriver/devmapper/driver.go @@ -90,6 +90,13 @@ func (d *Driver) Create(id, parent string) error { } func (d *Driver) Remove(id string) error { + if !d.DeviceSet.HasDevice(id) { + // Consider removing a non-existing device a no-op + // This is useful to be able to progress on container removal + // if the underlying device has gone away due to earlier errors + return nil + } + // Sink the float from create in case no Get() call was made if err := d.DeviceSet.UnmountDevice(id, UnmountSink); err != nil { return err diff --git a/integration/commands_test.go b/integration/commands_test.go index 9f7a41384..f254973b4 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -930,7 +930,7 @@ run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ] // #2098 - Docker cidFiles only contain short version of the containerId //sudo docker run --cidfile /tmp/docker_test.cid ubuntu echo "test" // TestRunCidFile tests that run --cidfile returns the longid -func TestRunCidFile(t *testing.T) { +func TestRunCidFileCheckIDLength(t *testing.T) { stdout, stdoutPipe := io.Pipe() tmpDir, err := ioutil.TempDir("", "TestRunCidFile") @@ -979,6 +979,35 @@ func TestRunCidFile(t *testing.T) { } +// Ensure that CIDFile gets deleted if it's empty +// Perform this test by making `docker run` fail +func TestRunCidFileCleanupIfEmpty(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "TestRunCidFile") + if err != nil { + t.Fatal(err) + } + tmpCidFile := path.Join(tmpDir, "cid") + + cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalEngine, t) + + c := make(chan struct{}) + go func() { + defer close(c) + if err := cli.CmdRun("--cidfile", tmpCidFile, unitTestImageID); err == nil { + t.Fatal("running without a command should haveve failed") + } + if _, err := os.Stat(tmpCidFile); err == nil { + t.Fatalf("empty CIDFile '%s' should've been deleted", tmpCidFile) + } + }() + defer os.RemoveAll(tmpDir) + + setTimeout(t, "CmdRun timed out", 5*time.Second, func() { + <-c + }) +} + func TestContainerOrphaning(t *testing.T) { // setup a temporary directory diff --git a/integration/container_test.go b/integration/container_test.go index 4efb95a2a..980fb4419 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -434,28 +434,6 @@ func TestOutput(t *testing.T) { } } -func TestContainerNetwork(t *testing.T) { - runtime := mkRuntime(t) - defer nuke(runtime) - container, _, err := runtime.Create( - &runconfig.Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"ping", "-c", "1", "127.0.0.1"}, - }, - "", - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container) - if err := container.Run(); err != nil { - t.Fatal(err) - } - if code := container.State.GetExitCode(); code != 0 { - t.Fatalf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", code) - } -} - func TestKillDifferentUser(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) @@ -1523,6 +1501,53 @@ func TestVolumesFromWithVolumes(t *testing.T) { } } +func TestContainerNetwork(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + container, _, err := runtime.Create( + &runconfig.Config{ + Image: GetTestImage(runtime).ID, + // If I change this to ping 8.8.8.8 it fails. Any idea why? - timthelion + Cmd: []string{"ping", "-c", "1", "127.0.0.1"}, + }, + "", + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + if err := container.Run(); err != nil { + t.Fatal(err) + } + if code := container.State.GetExitCode(); code != 0 { + t.Fatalf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", code) + } +} + +// Issue #4681 +func TestLoopbackFunctionsWhenNetworkingIsDissabled(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + container, _, err := runtime.Create( + &runconfig.Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"ping", "-c", "1", "127.0.0.1"}, + NetworkDisabled: true, + }, + "", + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + if err := container.Run(); err != nil { + t.Fatal(err) + } + if code := container.State.GetExitCode(); code != 0 { + t.Fatalf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", code) + } +} + func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { eng := NewTestEngine(t) runtime := mkRuntimeFromEngine(eng, t) diff --git a/integration/server_test.go b/integration/server_test.go index 69a90527b..e9781777e 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -281,6 +281,63 @@ func TestCommit(t *testing.T) { } } +func TestMergeConfigOnCommit(t *testing.T) { + eng := NewTestEngine(t) + runtime := mkRuntimeFromEngine(eng, t) + defer runtime.Nuke() + + container1, _, _ := mkContainer(runtime, []string{"-e", "FOO=bar", unitTestImageID, "echo test > /tmp/foo"}, t) + defer runtime.Destroy(container1) + + config, _, _, err := runconfig.Parse([]string{container1.ID, "cat /tmp/foo"}, nil) + if err != nil { + t.Error(err) + } + + job := eng.Job("commit", container1.ID) + job.Setenv("repo", "testrepo") + job.Setenv("tag", "testtag") + job.SetenvJson("config", config) + var newId string + job.Stdout.AddString(&newId) + if err := job.Run(); err != nil { + t.Error(err) + } + + container2, _, _ := mkContainer(runtime, []string{newId}, t) + defer runtime.Destroy(container2) + + job = eng.Job("inspect", container1.Name, "container") + baseContainer, _ := job.Stdout.AddEnv() + if err := job.Run(); err != nil { + t.Error(err) + } + + job = eng.Job("inspect", container2.Name, "container") + commitContainer, _ := job.Stdout.AddEnv() + if err := job.Run(); err != nil { + t.Error(err) + } + + baseConfig := baseContainer.GetSubEnv("Config") + commitConfig := commitContainer.GetSubEnv("Config") + + if commitConfig.Get("Env") != baseConfig.Get("Env") { + t.Fatalf("Env config in committed container should be %v, was %v", + baseConfig.Get("Env"), commitConfig.Get("Env")) + } + + if baseConfig.Get("Cmd") != "[\"echo test \\u003e /tmp/foo\"]" { + t.Fatalf("Cmd in base container should be [\"echo test \\u003e /tmp/foo\"], was %s", + baseConfig.Get("Cmd")) + } + + if commitConfig.Get("Cmd") != "[\"cat /tmp/foo\"]" { + t.Fatalf("Cmd in committed container should be [\"cat /tmp/foo\"], was %s", + commitConfig.Get("Cmd")) + } +} + func TestRestartKillWait(t *testing.T) { eng := NewTestEngine(t) srv := mkServerFromEngine(eng, t) diff --git a/pkg/libcontainer/apparmor/setup.go b/pkg/libcontainer/apparmor/setup.go index e07759cc6..4e1c95143 100644 --- a/pkg/libcontainer/apparmor/setup.go +++ b/pkg/libcontainer/apparmor/setup.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "os/exec" + "path" ) const DefaultProfilePath = "/etc/apparmor.d/docker" @@ -85,6 +86,11 @@ func InstallDefaultProfile() error { return nil } + // Make sure /etc/apparmor.d exists + if err := os.MkdirAll(path.Dir(DefaultProfilePath), 0755); err != nil { + return err + } + if err := ioutil.WriteFile(DefaultProfilePath, []byte(DefaultProfile), 0644); err != nil { return err } diff --git a/pkg/libcontainer/network/loopback.go b/pkg/libcontainer/network/loopback.go new file mode 100644 index 000000000..6215061dc --- /dev/null +++ b/pkg/libcontainer/network/loopback.go @@ -0,0 +1,24 @@ +package network + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" +) + +// Loopback is a network strategy that provides a basic loopback device +type Loopback struct { +} + +func (l *Loopback) Create(n *libcontainer.Network, nspid int, context libcontainer.Context) error { + return nil +} + +func (l *Loopback) Initialize(config *libcontainer.Network, context libcontainer.Context) error { + if err := SetMtu("lo", config.Mtu); err != nil { + return fmt.Errorf("set lo mtu to %d %s", config.Mtu, err) + } + if err := InterfaceUp("lo"); err != nil { + return fmt.Errorf("lo up %s", err) + } + return nil +} diff --git a/pkg/libcontainer/network/strategy.go b/pkg/libcontainer/network/strategy.go index 234fcc0aa..693790d28 100644 --- a/pkg/libcontainer/network/strategy.go +++ b/pkg/libcontainer/network/strategy.go @@ -10,7 +10,8 @@ var ( ) var strategies = map[string]NetworkStrategy{ - "veth": &Veth{}, + "veth": &Veth{}, + "loopback": &Loopback{}, } // NetworkStrategy represents a specific network configuration for diff --git a/pkg/libcontainer/network/veth.go b/pkg/libcontainer/network/veth.go index 3ab1b2393..3df0cd61e 100644 --- a/pkg/libcontainer/network/veth.go +++ b/pkg/libcontainer/network/veth.go @@ -68,12 +68,6 @@ func (v *Veth) Initialize(config *libcontainer.Network, context libcontainer.Con if err := InterfaceUp("eth0"); err != nil { return fmt.Errorf("eth0 up %s", err) } - if err := SetMtu("lo", config.Mtu); err != nil { - return fmt.Errorf("set lo mtu to %d %s", config.Mtu, err) - } - if err := InterfaceUp("lo"); err != nil { - return fmt.Errorf("lo up %s", err) - } if config.Gateway != "" { if err := SetDefaultGateway(config.Gateway); err != nil { return fmt.Errorf("set gateway to %s %s", config.Gateway, err) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 336fc1eaa..09f85e214 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -48,7 +48,9 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol return fmt.Errorf("setctty %s", err) } } - if err := system.ParentDeathSignal(); err != nil { + // this is our best effort to let the process know that the parent has died and that it + // should it should act on it how it sees fit + if err := system.ParentDeathSignal(uintptr(syscall.SIGTERM)); err != nil { return fmt.Errorf("parent death signal %s", err) } if err := setupNewMountNamespace(rootfs, console, container.ReadonlyFs, container.NoPivotRoot); err != nil { @@ -124,7 +126,11 @@ func setupNetwork(container *libcontainer.Container, context libcontainer.Contex if err != nil { return err } - return strategy.Initialize(config, context) + + err1 := strategy.Initialize(config, context) + if err1 != nil { + return err1 + } } return nil } diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 83577cfa8..072188ecd 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -46,10 +46,8 @@ func setupNewMountNamespace(rootfs, console string, readonly, noPivotRoot bool) if err := setupDev(rootfs); err != nil { return err } - if console != "" { - if err := setupPtmx(rootfs, console); err != nil { - return err - } + if err := setupPtmx(rootfs, console); err != nil { + return err } if err := system.Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) @@ -245,8 +243,10 @@ func setupPtmx(rootfs, console string) error { if err := os.Symlink("pts/ptmx", ptmx); err != nil { return fmt.Errorf("symlink dev ptmx %s", err) } - if err := setupConsole(rootfs, console); err != nil { - return err + if console != "" { + if err := setupConsole(rootfs, console); err != nil { + return err + } } return nil } diff --git a/pkg/listenbuffer/buffer.go b/pkg/listenbuffer/buffer.go index c350805a7..17572c8a0 100644 --- a/pkg/listenbuffer/buffer.go +++ b/pkg/listenbuffer/buffer.go @@ -5,15 +5,10 @@ */ package listenbuffer -import ( - "fmt" - "net" - "time" -) +import "net" -// NewListenBuffer returns a listener listening on addr with the protocol. It sets the -// timeout to wait on first connection before an error is returned -func NewListenBuffer(proto, addr string, activate chan struct{}, timeout time.Duration) (net.Listener, error) { +// NewListenBuffer returns a listener listening on addr with the protocol. +func NewListenBuffer(proto, addr string, activate chan struct{}) (net.Listener, error) { wrapped, err := net.Listen(proto, addr) if err != nil { return nil, err @@ -22,7 +17,6 @@ func NewListenBuffer(proto, addr string, activate chan struct{}, timeout time.Du return &defaultListener{ wrapped: wrapped, activate: activate, - timeout: timeout, }, nil } @@ -30,7 +24,6 @@ type defaultListener struct { wrapped net.Listener // the real listener to wrap ready bool // is the listner ready to start accpeting connections activate chan struct{} - timeout time.Duration // how long to wait before we consider this an error } func (l *defaultListener) Close() error { @@ -47,15 +40,7 @@ func (l *defaultListener) Accept() (net.Conn, error) { if l.ready { return l.wrapped.Accept() } - - select { - case <-time.After(l.timeout): - // close the connection so any clients are disconnected - l.Close() - return nil, fmt.Errorf("timeout (%s) reached waiting for listener to become ready", l.timeout.String()) - case <-l.activate: - l.ready = true - return l.Accept() - } - panic("unreachable") + <-l.activate + l.ready = true + return l.Accept() } diff --git a/pkg/mflag/example/example.go b/pkg/mflag/example/example.go index ed940e8d7..ce9dd30e4 100644 --- a/pkg/mflag/example/example.go +++ b/pkg/mflag/example/example.go @@ -13,7 +13,8 @@ var ( func init() { flag.Bool([]string{"#hp", "#-halp"}, false, "display the halp") - flag.BoolVar(&b, []string{"b"}, false, "a simple bool") + flag.BoolVar(&b, []string{"b", "#bal", "#bol", "-bal"}, false, "a simple bool") + flag.BoolVar(&b, []string{"g", "#gil"}, false, "a simple bool") flag.BoolVar(&b2, []string{"#-bool"}, false, "a simple bool") flag.IntVar(&i, []string{"-integer", "-number"}, -1, "a simple integer") flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index 7125c030e..581041065 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -805,9 +805,20 @@ func (f *FlagSet) parseOne() (bool, string, error) { f.actual = make(map[string]*Flag) } f.actual[name] = flag - for _, n := range flag.Names { + for i, n := range flag.Names { if n == fmt.Sprintf("#%s", name) { - fmt.Fprintf(f.out(), "Warning: '-%s' is deprecated, it will be removed soon. See usage.\n", name) + replacement := "" + for j := i; j < len(flag.Names); j++ { + if flag.Names[j][0] != '#' { + replacement = flag.Names[j] + break + } + } + if replacement != "" { + fmt.Fprintf(f.out(), "Warning: '-%s' is deprecated, it will be replaced by '-%s' soon. See usage.\n", name, replacement) + } else { + fmt.Fprintf(f.out(), "Warning: '-%s' is deprecated, it will be removed soon. See usage.\n", name) + } } } return true, "", nil diff --git a/pkg/signal/signal.go b/pkg/signal/signal.go new file mode 100644 index 000000000..63337542d --- /dev/null +++ b/pkg/signal/signal.go @@ -0,0 +1,19 @@ +package signal + +import ( + "os" + "os/signal" +) + +func CatchAll(sigc chan os.Signal) { + handledSigs := []os.Signal{} + for _, s := range SignalMap { + handledSigs = append(handledSigs, s) + } + signal.Notify(sigc, handledSigs...) +} + +func StopCatch(sigc chan os.Signal) { + signal.Stop(sigc) + close(sigc) +} diff --git a/pkg/signal/signal_darwin.go b/pkg/signal/signal_darwin.go new file mode 100644 index 000000000..fcd3a8f2c --- /dev/null +++ b/pkg/signal/signal_darwin.go @@ -0,0 +1,40 @@ +package signal + +import ( + "syscall" +) + +var SignalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUG": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CONT": syscall.SIGCONT, + "EMT": syscall.SIGEMT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INFO": syscall.SIGINFO, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "PIPE": syscall.SIGPIPE, + "PROF": syscall.SIGPROF, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, +} diff --git a/pkg/signal/signal_freebsd.go b/pkg/signal/signal_freebsd.go new file mode 100644 index 000000000..da042d7e7 --- /dev/null +++ b/pkg/signal/signal_freebsd.go @@ -0,0 +1,44 @@ +package signal + +import ( + "os" + "os/signal" + "syscall" +) + +var SignalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUF": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CONT": syscall.SIGCONT, + "EMT": syscall.SIGEMT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INFO": syscall.SIGINFO, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "LWP": syscall.SIGLWP, + "PIPE": syscall.SIGPIPE, + "PROF": syscall.SIGPROF, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "THR": syscall.SIGTHR, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, +} diff --git a/pkg/signal/signal_linux.go b/pkg/signal/signal_linux.go new file mode 100644 index 000000000..a62f79d4a --- /dev/null +++ b/pkg/signal/signal_linux.go @@ -0,0 +1,43 @@ +package signal + +import ( + "syscall" +) + +var SignalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUS": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CLD": syscall.SIGCLD, + "CONT": syscall.SIGCONT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "PIPE": syscall.SIGPIPE, + "POLL": syscall.SIGPOLL, + "PROF": syscall.SIGPROF, + "PWR": syscall.SIGPWR, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STKFLT": syscall.SIGSTKFLT, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "UNUSED": syscall.SIGUNUSED, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, +} diff --git a/pkg/signal/signal_unsupported.go b/pkg/signal/signal_unsupported.go new file mode 100644 index 000000000..99f946597 --- /dev/null +++ b/pkg/signal/signal_unsupported.go @@ -0,0 +1,9 @@ +// +build !linux,!darwin,!freebsd + +package signal + +import ( + "syscall" +) + +var SignalMap = map[string]syscall.Signal{} diff --git a/pkg/system/calls_linux.go b/pkg/system/calls_linux.go index bf667c535..43c00ed55 100644 --- a/pkg/system/calls_linux.go +++ b/pkg/system/calls_linux.go @@ -115,8 +115,8 @@ func Mknod(path string, mode uint32, dev int) error { return syscall.Mknod(path, mode, dev) } -func ParentDeathSignal() error { - if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGKILL), 0); err != 0 { +func ParentDeathSignal(sig uintptr) error { + if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, sig, 0); err != 0 { return err } return nil diff --git a/server.go b/server.go index 2c8a7ee4f..c5ad24c4b 100644 --- a/server.go +++ b/server.go @@ -8,6 +8,7 @@ import ( "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/pkg/graphdb" + "github.com/dotcloud/docker/pkg/signal" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" @@ -18,7 +19,7 @@ import ( "net/url" "os" "os/exec" - "os/signal" + gosignal "os/signal" "path" "path/filepath" "runtime" @@ -47,7 +48,7 @@ func InitServer(job *engine.Job) engine.Status { } job.Logf("Setting up signal traps") c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) + gosignal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) go func() { sig := <-c log.Printf("Received signal '%v', exiting\n", sig) @@ -122,56 +123,30 @@ func (v *simpleVersionInfo) Version() string { // for the container to exit. // If a signal is given, then just send it to the container and return. func (srv *Server) ContainerKill(job *engine.Job) engine.Status { - signalMap := map[string]syscall.Signal{ - "HUP": syscall.SIGHUP, - "INT": syscall.SIGINT, - "QUIT": syscall.SIGQUIT, - "ILL": syscall.SIGILL, - "TRAP": syscall.SIGTRAP, - "ABRT": syscall.SIGABRT, - "BUS": syscall.SIGBUS, - "FPE": syscall.SIGFPE, - "KILL": syscall.SIGKILL, - "USR1": syscall.SIGUSR1, - "SEGV": syscall.SIGSEGV, - "USR2": syscall.SIGUSR2, - "PIPE": syscall.SIGPIPE, - "ALRM": syscall.SIGALRM, - "TERM": syscall.SIGTERM, - //"STKFLT": syscall.SIGSTKFLT, - "CHLD": syscall.SIGCHLD, - "CONT": syscall.SIGCONT, - "STOP": syscall.SIGSTOP, - "TSTP": syscall.SIGTSTP, - "TTIN": syscall.SIGTTIN, - "TTOU": syscall.SIGTTOU, - "URG": syscall.SIGURG, - "XCPU": syscall.SIGXCPU, - "XFSZ": syscall.SIGXFSZ, - "VTALRM": syscall.SIGVTALRM, - "PROF": syscall.SIGPROF, - "WINCH": syscall.SIGWINCH, - "IO": syscall.SIGIO, - //"PWR": syscall.SIGPWR, - "SYS": syscall.SIGSYS, - } - if n := len(job.Args); n < 1 || n > 2 { return job.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name) } - name := job.Args[0] - var sig uint64 + var ( + name = job.Args[0] + sig uint64 + err error + ) + + // If we have a signal, look at it. Otherwise, do nothing if len(job.Args) == 2 && job.Args[1] != "" { - sig = uint64(signalMap[job.Args[1]]) - if sig == 0 { - var err error - // The largest legal signal is 31, so let's parse on 5 bits - sig, err = strconv.ParseUint(job.Args[1], 10, 5) - if err != nil { + // Check if we passed the singal as a number: + // The largest legal signal is 31, so let's parse on 5 bits + sig, err = strconv.ParseUint(job.Args[1], 10, 5) + if err != nil { + // The signal is not a number, treat it as a string + sig = uint64(signal.SignalMap[job.Args[1]]) + if sig == 0 { return job.Errorf("Invalid signal: %s", job.Args[1]) } + } } + if container := srv.runtime.Get(name); container != nil { // If no signal is passed, or SIGKILL, perform regular Kill (SIGKILL + wait()) if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL { @@ -1039,12 +1014,17 @@ func (srv *Server) ContainerCommit(job *engine.Job) engine.Status { if container == nil { return job.Errorf("No such container: %s", name) } - var config runconfig.Config - if err := job.GetenvJson("config", &config); err != nil { + var config = container.Config + var newConfig runconfig.Config + if err := job.GetenvJson("config", &newConfig); err != nil { return job.Error(err) } - img, err := srv.runtime.Commit(container, job.Getenv("repo"), job.Getenv("tag"), job.Getenv("comment"), job.Getenv("author"), &config) + if err := runconfig.Merge(&newConfig, config); err != nil { + return job.Error(err) + } + + img, err := srv.runtime.Commit(container, job.Getenv("repo"), job.Getenv("tag"), job.Getenv("comment"), job.Getenv("author"), &newConfig) if err != nil { return job.Error(err) } @@ -2390,7 +2370,13 @@ func (srv *Server) IsRunning() bool { } func (srv *Server) Close() error { + if srv == nil { + return nil + } srv.SetRunning(false) + if srv.runtime == nil { + return nil + } return srv.runtime.Close() } diff --git a/utils/jsonmessage.go b/utils/jsonmessage.go index 9050dda74..f84cc42c7 100644 --- a/utils/jsonmessage.go +++ b/utils/jsonmessage.go @@ -85,7 +85,7 @@ func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error { return jm.Error } var endl string - if isTerminal { + if isTerminal && jm.Stream == "" { // [2K = erase entire current line fmt.Fprintf(out, "%c[2K\r", 27) endl = "\r" diff --git a/utils/signal.go b/utils/signal.go deleted file mode 100644 index 0cac7d113..000000000 --- a/utils/signal.go +++ /dev/null @@ -1,11 +0,0 @@ -package utils - -import ( - "os" - "os/signal" -) - -func StopCatch(sigc chan os.Signal) { - signal.Stop(sigc) - close(sigc) -} diff --git a/utils/signal_darwin.go b/utils/signal_darwin.go deleted file mode 100644 index 28730db8e..000000000 --- a/utils/signal_darwin.go +++ /dev/null @@ -1,44 +0,0 @@ -package utils - -import ( - "os" - "os/signal" - "syscall" -) - -func CatchAll(sigc chan os.Signal) { - signal.Notify(sigc, - syscall.SIGABRT, - syscall.SIGALRM, - syscall.SIGBUS, - syscall.SIGCHLD, - syscall.SIGCONT, - syscall.SIGEMT, - syscall.SIGFPE, - syscall.SIGHUP, - syscall.SIGILL, - syscall.SIGINFO, - syscall.SIGINT, - syscall.SIGIO, - syscall.SIGIOT, - syscall.SIGKILL, - syscall.SIGPIPE, - syscall.SIGPROF, - syscall.SIGQUIT, - syscall.SIGSEGV, - syscall.SIGSTOP, - syscall.SIGSYS, - syscall.SIGTERM, - syscall.SIGTRAP, - syscall.SIGTSTP, - syscall.SIGTTIN, - syscall.SIGTTOU, - syscall.SIGURG, - syscall.SIGUSR1, - syscall.SIGUSR2, - syscall.SIGVTALRM, - syscall.SIGWINCH, - syscall.SIGXCPU, - syscall.SIGXFSZ, - ) -} diff --git a/utils/signal_linux.go b/utils/signal_linux.go deleted file mode 100644 index 26cfd5696..000000000 --- a/utils/signal_linux.go +++ /dev/null @@ -1,47 +0,0 @@ -package utils - -import ( - "os" - "os/signal" - "syscall" -) - -func CatchAll(sigc chan os.Signal) { - signal.Notify(sigc, - syscall.SIGABRT, - syscall.SIGALRM, - syscall.SIGBUS, - syscall.SIGCHLD, - syscall.SIGCLD, - syscall.SIGCONT, - syscall.SIGFPE, - syscall.SIGHUP, - syscall.SIGILL, - syscall.SIGINT, - syscall.SIGIO, - syscall.SIGIOT, - syscall.SIGKILL, - syscall.SIGPIPE, - syscall.SIGPOLL, - syscall.SIGPROF, - syscall.SIGPWR, - syscall.SIGQUIT, - syscall.SIGSEGV, - syscall.SIGSTKFLT, - syscall.SIGSTOP, - syscall.SIGSYS, - syscall.SIGTERM, - syscall.SIGTRAP, - syscall.SIGTSTP, - syscall.SIGTTIN, - syscall.SIGTTOU, - syscall.SIGUNUSED, - syscall.SIGURG, - syscall.SIGUSR1, - syscall.SIGUSR2, - syscall.SIGVTALRM, - syscall.SIGWINCH, - syscall.SIGXCPU, - syscall.SIGXFSZ, - ) -}