Explorar el Código

Merge pull request #15666 from vdemeester/3519-configurable-escape

Implement configurable escape key for attach/exec
Sebastiaan van Stijn hace 9 años
padre
commit
db738dd77f

+ 6 - 0
api/client/attach.go

@@ -18,6 +18,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 	cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, Cli.DockerCommands["attach"].Description, true)
 	cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, Cli.DockerCommands["attach"].Description, true)
 	noStdin := cmd.Bool([]string{"-no-stdin"}, false, "Do not attach STDIN")
 	noStdin := cmd.Bool([]string{"-no-stdin"}, false, "Do not attach STDIN")
 	proxy := cmd.Bool([]string{"-sig-proxy"}, true, "Proxy all received signals to the process")
 	proxy := cmd.Bool([]string{"-sig-proxy"}, true, "Proxy all received signals to the process")
+	detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
 
 
 	cmd.Require(flag.Exact, 1)
 	cmd.Require(flag.Exact, 1)
 
 
@@ -46,12 +47,17 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 		}
 		}
 	}
 	}
 
 
+	if *detachKeys != "" {
+		cli.configFile.DetachKeys = *detachKeys
+	}
+
 	options := types.ContainerAttachOptions{
 	options := types.ContainerAttachOptions{
 		ContainerID: cmd.Arg(0),
 		ContainerID: cmd.Arg(0),
 		Stream:      true,
 		Stream:      true,
 		Stdin:       !*noStdin && c.Config.OpenStdin,
 		Stdin:       !*noStdin && c.Config.OpenStdin,
 		Stdout:      true,
 		Stdout:      true,
 		Stderr:      true,
 		Stderr:      true,
+		DetachKeys:  cli.configFile.DetachKeys,
 	}
 	}
 
 
 	var in io.ReadCloser
 	var in io.ReadCloser

+ 8 - 0
api/client/exec.go

@@ -16,6 +16,7 @@ import (
 // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
 // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
 func (cli *DockerCli) CmdExec(args ...string) error {
 func (cli *DockerCli) CmdExec(args ...string) error {
 	cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
 	cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
+	detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
 
 
 	execConfig, err := runconfig.ParseExec(cmd, args)
 	execConfig, err := runconfig.ParseExec(cmd, args)
 	// just in case the ParseExec does not exit
 	// just in case the ParseExec does not exit
@@ -23,6 +24,13 @@ func (cli *DockerCli) CmdExec(args ...string) error {
 		return Cli.StatusError{StatusCode: 1}
 		return Cli.StatusError{StatusCode: 1}
 	}
 	}
 
 
+	if *detachKeys != "" {
+		cli.configFile.DetachKeys = *detachKeys
+	}
+
+	// Send client escape keys
+	execConfig.DetachKeys = cli.configFile.DetachKeys
+
 	response, err := cli.client.ContainerExecCreate(*execConfig)
 	response, err := cli.client.ContainerExecCreate(*execConfig)
 	if err != nil {
 	if err != nil {
 		return err
 		return err

+ 3 - 0
api/client/lib/container_attach.go

@@ -24,6 +24,9 @@ func (cli *Client) ContainerAttach(options types.ContainerAttachOptions) (types.
 	if options.Stderr {
 	if options.Stderr {
 		query.Set("stderr", "1")
 		query.Set("stderr", "1")
 	}
 	}
+	if options.DetachKeys != "" {
+		query.Set("detachKeys", options.DetachKeys)
+	}
 
 
 	headers := map[string][]string{"Content-Type": {"text/plain"}}
 	headers := map[string][]string{"Content-Type": {"text/plain"}}
 	return cli.postHijacked("/containers/"+options.ContainerID+"/attach", query, nil, headers)
 	return cli.postHijacked("/containers/"+options.ContainerID+"/attach", query, nil, headers)

+ 6 - 0
api/client/run.go

@@ -74,6 +74,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		flDetach     = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
 		flDetach     = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
 		flSigProxy   = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
 		flSigProxy   = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
 		flName       = cmd.String([]string{"-name"}, "", "Assign a name to the container")
 		flName       = cmd.String([]string{"-name"}, "", "Assign a name to the container")
+		flDetachKeys = cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
 		flAttach     *opts.ListOpts
 		flAttach     *opts.ListOpts
 
 
 		ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
 		ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
@@ -188,12 +189,17 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 			}
 			}
 		}
 		}
 
 
+		if *flDetachKeys != "" {
+			cli.configFile.DetachKeys = *flDetachKeys
+		}
+
 		options := types.ContainerAttachOptions{
 		options := types.ContainerAttachOptions{
 			ContainerID: createResponse.ID,
 			ContainerID: createResponse.ID,
 			Stream:      true,
 			Stream:      true,
 			Stdin:       config.AttachStdin,
 			Stdin:       config.AttachStdin,
 			Stdout:      config.AttachStdout,
 			Stdout:      config.AttachStdout,
 			Stderr:      config.AttachStderr,
 			Stderr:      config.AttachStderr,
+			DetachKeys:  cli.configFile.DetachKeys,
 		}
 		}
 
 
 		resp, err := cli.client.ContainerAttach(options)
 		resp, err := cli.client.ContainerAttach(options)

+ 6 - 0
api/client/start.go

@@ -49,6 +49,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 	cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true)
 	cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true)
 	attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
 	attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
 	openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
 	openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
+	detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
 	cmd.Require(flag.Min, 1)
 	cmd.Require(flag.Min, 1)
 
 
 	cmd.ParseFlags(args, true)
 	cmd.ParseFlags(args, true)
@@ -72,12 +73,17 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 			defer signal.StopCatch(sigc)
 			defer signal.StopCatch(sigc)
 		}
 		}
 
 
+		if *detachKeys != "" {
+			cli.configFile.DetachKeys = *detachKeys
+		}
+
 		options := types.ContainerAttachOptions{
 		options := types.ContainerAttachOptions{
 			ContainerID: containerID,
 			ContainerID: containerID,
 			Stream:      true,
 			Stream:      true,
 			Stdin:       *openStdin && c.Config.OpenStdin,
 			Stdin:       *openStdin && c.Config.OpenStdin,
 			Stdout:      true,
 			Stdout:      true,
 			Stderr:      true,
 			Stderr:      true,
+			DetachKeys:  cli.configFile.DetachKeys,
 		}
 		}
 
 
 		var in io.ReadCloser
 		var in io.ReadCloser

+ 36 - 13
api/server/router/container/container_routes.go

@@ -19,6 +19,7 @@ import (
 	derr "github.com/docker/docker/errors"
 	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
+	"github.com/docker/docker/pkg/term"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/utils"
 	"github.com/docker/docker/utils"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -420,21 +421,32 @@ func (s *containerRouter) postContainersResize(ctx context.Context, w http.Respo
 }
 }
 
 
 func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-	if err := httputils.ParseForm(r); err != nil {
+	err := httputils.ParseForm(r)
+	if err != nil {
 		return err
 		return err
 	}
 	}
 	containerName := vars["name"]
 	containerName := vars["name"]
 
 
 	_, upgrade := r.Header["Upgrade"]
 	_, upgrade := r.Header["Upgrade"]
 
 
+	keys := []byte{}
+	detachKeys := r.FormValue("detachKeys")
+	if detachKeys != "" {
+		keys, err = term.ToBytes(detachKeys)
+		if err != nil {
+			logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys)
+		}
+	}
+
 	attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{
 	attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{
-		Hijacker:  w.(http.Hijacker),
-		Upgrade:   upgrade,
-		UseStdin:  httputils.BoolValue(r, "stdin"),
-		UseStdout: httputils.BoolValue(r, "stdout"),
-		UseStderr: httputils.BoolValue(r, "stderr"),
-		Logs:      httputils.BoolValue(r, "logs"),
-		Stream:    httputils.BoolValue(r, "stream"),
+		Hijacker:   w.(http.Hijacker),
+		Upgrade:    upgrade,
+		UseStdin:   httputils.BoolValue(r, "stdin"),
+		UseStdout:  httputils.BoolValue(r, "stdout"),
+		UseStderr:  httputils.BoolValue(r, "stderr"),
+		Logs:       httputils.BoolValue(r, "logs"),
+		Stream:     httputils.BoolValue(r, "stream"),
+		DetachKeys: keys,
 	}
 	}
 
 
 	return s.backend.ContainerAttachWithLogs(containerName, attachWithLogsConfig)
 	return s.backend.ContainerAttachWithLogs(containerName, attachWithLogsConfig)
@@ -450,15 +462,26 @@ func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.Respons
 		return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
 		return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
 	}
 	}
 
 
+	var keys []byte
+	var err error
+	detachKeys := r.FormValue("detachKeys")
+	if detachKeys != "" {
+		keys, err = term.ToBytes(detachKeys)
+		if err != nil {
+			logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys)
+		}
+	}
+
 	h := websocket.Handler(func(ws *websocket.Conn) {
 	h := websocket.Handler(func(ws *websocket.Conn) {
 		defer ws.Close()
 		defer ws.Close()
 
 
 		wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{
 		wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{
-			InStream:  ws,
-			OutStream: ws,
-			ErrStream: ws,
-			Logs:      httputils.BoolValue(r, "logs"),
-			Stream:    httputils.BoolValue(r, "stream"),
+			InStream:   ws,
+			OutStream:  ws,
+			ErrStream:  ws,
+			Logs:       httputils.BoolValue(r, "logs"),
+			Stream:     httputils.BoolValue(r, "stream"),
+			DetachKeys: keys,
 		}
 		}
 
 
 		if err := s.backend.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil {
 		if err := s.backend.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil {

+ 1 - 0
api/types/client.go

@@ -17,6 +17,7 @@ type ContainerAttachOptions struct {
 	Stdin       bool
 	Stdin       bool
 	Stdout      bool
 	Stdout      bool
 	Stderr      bool
 	Stderr      bool
+	DetachKeys  string
 }
 }
 
 
 // ContainerCommitOptions holds parameters to commit changes into a container.
 // ContainerCommitOptions holds parameters to commit changes into a container.

+ 1 - 0
api/types/configs.go

@@ -45,5 +45,6 @@ type ExecConfig struct {
 	AttachStderr bool     // Attach the standard output
 	AttachStderr bool     // Attach the standard output
 	AttachStdout bool     // Attach the standard error
 	AttachStdout bool     // Attach the standard error
 	Detach       bool     // Execute in detach mode
 	Detach       bool     // Execute in detach mode
+	DetachKeys   string   // Escape keys for detach
 	Cmd          []string // Execution commands and args
 	Cmd          []string // Execution commands and args
 }
 }

+ 1 - 0
cliconfig/config.go

@@ -51,6 +51,7 @@ type ConfigFile struct {
 	HTTPHeaders  map[string]string           `json:"HttpHeaders,omitempty"`
 	HTTPHeaders  map[string]string           `json:"HttpHeaders,omitempty"`
 	PsFormat     string                      `json:"psFormat,omitempty"`
 	PsFormat     string                      `json:"psFormat,omitempty"`
 	ImagesFormat string                      `json:"imagesFormat,omitempty"`
 	ImagesFormat string                      `json:"imagesFormat,omitempty"`
+	DetachKeys   string                      `json:"detachKeys,omitempty"`
 	filename     string                      // Note: not serialized - for internal use only
 	filename     string                      // Note: not serialized - for internal use only
 }
 }
 
 

+ 15 - 10
container/container.go

@@ -329,13 +329,13 @@ func (container *Container) GetExecIDs() []string {
 
 
 // Attach connects to the container's TTY, delegating to standard
 // Attach connects to the container's TTY, delegating to standard
 // streams or websockets depending on the configuration.
 // streams or websockets depending on the configuration.
-func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
-	return AttachStreams(container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr)
+func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error {
+	return AttachStreams(container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr, keys)
 }
 }
 
 
 // AttachStreams connects streams to a TTY.
 // AttachStreams connects streams to a TTY.
 // Used by exec too. Should this move somewhere else?
 // Used by exec too. Should this move somewhere else?
-func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
+func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error {
 	var (
 	var (
 		cStdout, cStderr io.ReadCloser
 		cStdout, cStderr io.ReadCloser
 		cStdin           io.WriteCloser
 		cStdin           io.WriteCloser
@@ -382,7 +382,7 @@ func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, t
 
 
 		var err error
 		var err error
 		if tty {
 		if tty {
-			_, err = copyEscapable(cStdin, stdin)
+			_, err = copyEscapable(cStdin, stdin, keys)
 		} else {
 		} else {
 			_, err = io.Copy(cStdin, stdin)
 			_, err = io.Copy(cStdin, stdin)
 
 
@@ -438,22 +438,27 @@ func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, t
 }
 }
 
 
 // Code c/c from io.Copy() modified to handle escape sequence
 // Code c/c from io.Copy() modified to handle escape sequence
-func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
+func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
+	if len(keys) == 0 {
+		// Default keys : ctrl-p ctrl-q
+		keys = []byte{16, 17}
+	}
 	buf := make([]byte, 32*1024)
 	buf := make([]byte, 32*1024)
 	for {
 	for {
 		nr, er := src.Read(buf)
 		nr, er := src.Read(buf)
 		if nr > 0 {
 		if nr > 0 {
 			// ---- Docker addition
 			// ---- Docker addition
-			// char 16 is C-p
-			if nr == 1 && buf[0] == 16 {
-				nr, er = src.Read(buf)
-				// char 17 is C-q
-				if nr == 1 && buf[0] == 17 {
+			for i, key := range keys {
+				if nr != 1 || buf[0] != key {
+					break
+				}
+				if i == len(keys)-1 {
 					if err := src.Close(); err != nil {
 					if err := src.Close(); err != nil {
 						return 0, err
 						return 0, err
 					}
 					}
 					return 0, nil
 					return 0, nil
 				}
 				}
+				nr, er = src.Read(buf)
 			}
 			}
 			// ---- End of docker
 			// ---- End of docker
 			nw, ew := dst.Write(buf[0:nr])
 			nw, ew := dst.Write(buf[0:nr])

+ 13 - 11
daemon/attach.go

@@ -15,13 +15,14 @@ import (
 
 
 // ContainerAttachWithLogsConfig holds the streams to use when connecting to a container to view logs.
 // ContainerAttachWithLogsConfig holds the streams to use when connecting to a container to view logs.
 type ContainerAttachWithLogsConfig struct {
 type ContainerAttachWithLogsConfig struct {
-	Hijacker  http.Hijacker
-	Upgrade   bool
-	UseStdin  bool
-	UseStdout bool
-	UseStderr bool
-	Logs      bool
-	Stream    bool
+	Hijacker   http.Hijacker
+	Upgrade    bool
+	UseStdin   bool
+	UseStdout  bool
+	UseStderr  bool
+	Logs       bool
+	Stream     bool
+	DetachKeys []byte
 }
 }
 
 
 // ContainerAttachWithLogs attaches to logs according to the config passed in. See ContainerAttachWithLogsConfig.
 // ContainerAttachWithLogs attaches to logs according to the config passed in. See ContainerAttachWithLogsConfig.
@@ -75,7 +76,7 @@ func (daemon *Daemon) ContainerAttachWithLogs(prefixOrName string, c *ContainerA
 		stderr = errStream
 		stderr = errStream
 	}
 	}
 
 
-	if err := daemon.attachWithLogs(container, stdin, stdout, stderr, c.Logs, c.Stream); err != nil {
+	if err := daemon.attachWithLogs(container, stdin, stdout, stderr, c.Logs, c.Stream, c.DetachKeys); err != nil {
 		fmt.Fprintf(outStream, "Error attaching: %s\n", err)
 		fmt.Fprintf(outStream, "Error attaching: %s\n", err)
 	}
 	}
 	return nil
 	return nil
@@ -87,6 +88,7 @@ type ContainerWsAttachWithLogsConfig struct {
 	InStream             io.ReadCloser
 	InStream             io.ReadCloser
 	OutStream, ErrStream io.Writer
 	OutStream, ErrStream io.Writer
 	Logs, Stream         bool
 	Logs, Stream         bool
+	DetachKeys           []byte
 }
 }
 
 
 // ContainerWsAttachWithLogs websocket connection
 // ContainerWsAttachWithLogs websocket connection
@@ -95,10 +97,10 @@ func (daemon *Daemon) ContainerWsAttachWithLogs(prefixOrName string, c *Containe
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	return daemon.attachWithLogs(container, c.InStream, c.OutStream, c.ErrStream, c.Logs, c.Stream)
+	return daemon.attachWithLogs(container, c.InStream, c.OutStream, c.ErrStream, c.Logs, c.Stream, c.DetachKeys)
 }
 }
 
 
-func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool) error {
+func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool, keys []byte) error {
 	if logs {
 	if logs {
 		logDriver, err := daemon.getLogger(container)
 		logDriver, err := daemon.getLogger(container)
 		if err != nil {
 		if err != nil {
@@ -144,7 +146,7 @@ func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.Re
 			}()
 			}()
 			stdinPipe = r
 			stdinPipe = r
 		}
 		}
-		<-container.Attach(stdinPipe, stdout, stderr)
+		<-container.Attach(stdinPipe, stdout, stderr, keys)
 		// If we are in stdinonce mode, wait for the process to end
 		// If we are in stdinonce mode, wait for the process to end
 		// otherwise, simply return
 		// otherwise, simply return
 		if container.Config.StdinOnce && !container.Config.Tty {
 		if container.Config.StdinOnce && !container.Config.Tty {

+ 12 - 1
daemon/exec.go

@@ -14,6 +14,7 @@ import (
 	derr "github.com/docker/docker/errors"
 	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/pkg/pools"
 	"github.com/docker/docker/pkg/pools"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/promise"
+	"github.com/docker/docker/pkg/term"
 )
 )
 
 
 func (d *Daemon) registerExecCommand(container *container.Container, config *exec.Config) {
 func (d *Daemon) registerExecCommand(container *container.Container, config *exec.Config) {
@@ -88,6 +89,14 @@ func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) {
 	cmd := strslice.New(config.Cmd...)
 	cmd := strslice.New(config.Cmd...)
 	entrypoint, args := d.getEntrypointAndArgs(strslice.New(), cmd)
 	entrypoint, args := d.getEntrypointAndArgs(strslice.New(), cmd)
 
 
+	keys := []byte{}
+	if config.DetachKeys != "" {
+		keys, err = term.ToBytes(config.DetachKeys)
+		if err != nil {
+			logrus.Warnf("Wrong escape keys provided (%s, error: %s) using default : ctrl-p ctrl-q", config.DetachKeys, err.Error())
+		}
+	}
+
 	processConfig := &execdriver.ProcessConfig{
 	processConfig := &execdriver.ProcessConfig{
 		CommonProcessConfig: execdriver.CommonProcessConfig{
 		CommonProcessConfig: execdriver.CommonProcessConfig{
 			Tty:        config.Tty,
 			Tty:        config.Tty,
@@ -103,6 +112,7 @@ func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) {
 	execConfig.OpenStderr = config.AttachStderr
 	execConfig.OpenStderr = config.AttachStderr
 	execConfig.ProcessConfig = processConfig
 	execConfig.ProcessConfig = processConfig
 	execConfig.ContainerID = container.ID
 	execConfig.ContainerID = container.ID
+	execConfig.DetachKeys = keys
 
 
 	d.registerExecCommand(container, execConfig)
 	d.registerExecCommand(container, execConfig)
 
 
@@ -158,7 +168,8 @@ func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.
 		ec.NewNopInputPipe()
 		ec.NewNopInputPipe()
 	}
 	}
 
 
-	attachErr := container.AttachStreams(ec.StreamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr)
+	attachErr := container.AttachStreams(ec.StreamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr, ec.DetachKeys)
+
 	execErr := make(chan error)
 	execErr := make(chan error)
 
 
 	// Note, the ExecConfig data will be removed when the container
 	// Note, the ExecConfig data will be removed when the container

+ 1 - 0
daemon/exec/exec.go

@@ -25,6 +25,7 @@ type Config struct {
 	OpenStdout    bool
 	OpenStdout    bool
 	CanRemove     bool
 	CanRemove     bool
 	ContainerID   string
 	ContainerID   string
+	DetachKeys    []byte
 
 
 	// waitStart will be closed immediately after the exec is really started.
 	// waitStart will be closed immediately after the exec is really started.
 	waitStart chan struct{}
 	waitStart chan struct{}

+ 18 - 3
docs/reference/api/docker_remote_api_v1.22.md

@@ -862,10 +862,9 @@ This endpoint returns a live stream of a container's resource usage statistics.
                "total_usage" : 36488948,
                "total_usage" : 36488948,
                "usage_in_kernelmode" : 20000000
                "usage_in_kernelmode" : 20000000
             },
             },
-            "system_cpu_usage" : 20091722000000000,
+            "system_cpu_usage" : 20091722000000000,
             "throttling_data" : {}
             "throttling_data" : {}
-         }
-      }
+         }      }
 
 
 Query Parameters:
 Query Parameters:
 
 
@@ -922,6 +921,12 @@ Start the container `id`
 
 
     HTTP/1.1 204 No Content
     HTTP/1.1 204 No Content
 
 
+Query Parameters:
+
+-   **detacheys** – Override the key sequence for detaching a
+        container. Format is a single character `[a-Z]` or `ctrl-<value>`
+        where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
+
 Status Codes:
 Status Codes:
 
 
 -   **204** – no error
 -   **204** – no error
@@ -1133,6 +1138,9 @@ Attach to the container `id`
 
 
 Query Parameters:
 Query Parameters:
 
 
+-   **detacheys** – Override the key sequence for detaching a
+        container. Format is a single character `[a-Z]` or `ctrl-<value>`
+        where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
 -   **logs** – 1/True/true or 0/False/false, return logs. Default `false`.
 -   **logs** – 1/True/true or 0/False/false, return logs. Default `false`.
 -   **stream** – 1/True/true or 0/False/false, return stream.
 -   **stream** – 1/True/true or 0/False/false, return stream.
         Default `false`.
         Default `false`.
@@ -1213,6 +1221,9 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet
 
 
 Query Parameters:
 Query Parameters:
 
 
+-   **detacheys** – Override the key sequence for detaching a
+        container. Format is a single character `[a-Z]` or `ctrl-<value>`
+        where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
 -   **logs** – 1/True/true or 0/False/false, return logs. Default `false`.
 -   **logs** – 1/True/true or 0/False/false, return logs. Default `false`.
 -   **stream** – 1/True/true or 0/False/false, return stream.
 -   **stream** – 1/True/true or 0/False/false, return stream.
         Default `false`.
         Default `false`.
@@ -2420,6 +2431,7 @@ Sets up an exec instance in a running container `id`
        "AttachStdin": false,
        "AttachStdin": false,
        "AttachStdout": true,
        "AttachStdout": true,
        "AttachStderr": true,
        "AttachStderr": true,
+       "DetachKeys": "ctrl-p,ctrl-q",
        "Tty": false,
        "Tty": false,
        "Cmd": [
        "Cmd": [
                      "date"
                      "date"
@@ -2441,6 +2453,9 @@ Json Parameters:
 -   **AttachStdin** - Boolean value, attaches to `stdin` of the `exec` command.
 -   **AttachStdin** - Boolean value, attaches to `stdin` of the `exec` command.
 -   **AttachStdout** - Boolean value, attaches to `stdout` of the `exec` command.
 -   **AttachStdout** - Boolean value, attaches to `stdout` of the `exec` command.
 -   **AttachStderr** - Boolean value, attaches to `stderr` of the `exec` command.
 -   **AttachStderr** - Boolean value, attaches to `stderr` of the `exec` command.
+-   **Detacheys** – Override the key sequence for detaching a
+        container. Format is a single character `[a-Z]` or `ctrl-<value>`
+        where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
 -   **Tty** - Boolean value to allocate a pseudo-TTY.
 -   **Tty** - Boolean value to allocate a pseudo-TTY.
 -   **Cmd** - Command to run specified as a string or an array of strings.
 -   **Cmd** - Command to run specified as a string or an array of strings.
 
 

+ 33 - 8
docs/reference/commandline/attach.md

@@ -14,9 +14,10 @@ parent = "smn_cli"
 
 
     Attach to a running container
     Attach to a running container
 
 
-      --help              Print usage
-      --no-stdin          Do not attach STDIN
-      --sig-proxy=true    Proxy all received signals to the process
+      --detach-keys="<sequence>"       Set up escape key sequence
+      --help                           Print usage
+      --no-stdin                       Do not attach STDIN
+      --sig-proxy=true                 Proxy all received signals to the process
 
 
 The `docker attach` command allows you to attach to a running container using
 The `docker attach` command allows you to attach to a running container using
 the container's ID or name, either to view its ongoing output or to control it
 the container's ID or name, either to view its ongoing output or to control it
@@ -24,11 +25,10 @@ interactively. You can attach to the same contained process multiple times
 simultaneously, screen sharing style, or quickly view the progress of your
 simultaneously, screen sharing style, or quickly view the progress of your
 detached  process.
 detached  process.
 
 
-You can detach from the container and leave it running with `CTRL-p CTRL-q`
-(for a quiet exit) or with `CTRL-c` if `--sig-proxy` is false.
-
-If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` to the
-container.
+To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the
+container. If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` to
+the container. You can detach from a container and leave it running using the
+using `CTRL-p CTRL-q` key sequence.
 
 
 > **Note:**
 > **Note:**
 > A process running as PID 1 inside a container is treated specially by
 > A process running as PID 1 inside a container is treated specially by
@@ -39,6 +39,31 @@ container.
 It is forbidden to redirect the standard input of a `docker attach` command
 It is forbidden to redirect the standard input of a `docker attach` command
 while attaching to a tty-enabled container (i.e.: launched with `-t`).
 while attaching to a tty-enabled container (i.e.: launched with `-t`).
 
 
+
+## Override the detach sequence
+
+If you want, you can configure a override the Docker key sequence for detach.
+This is is useful if the Docker default sequence conflicts with key squence you
+use for other applications. There are two ways to defines a your own detach key
+sequence, as a per-container override or as a configuration property on  your
+entire configuration.
+
+To override the sequence for an individual container, use the
+`--detach-keys="<sequence>"` flag with the `docker attach` command. The format of
+the `<sequence>` is either a letter [a-Z], or the `ctrl-` combined with any of
+the following:
+
+* `a-z` (a single lowercase alpha character )
+* `@` (ampersand)
+* `[` (left bracket)
+* `\\` (two backward slashes)
+*  `_` (underscore)
+* `^` (caret)
+
+These `a`, `ctrl-a`, `X`, or `ctrl-\\` values are all examples of valid key
+sequences. To configure a different configuration default key sequence for all
+containers, see [**Configuration file** section](cli.md#configuration-files).
+
 #### Examples
 #### Examples
 
 
     $ docker run -d --name topdemo ubuntu /usr/bin/top -b
     $ docker run -d --name topdemo ubuntu /usr/bin/top -b

+ 23 - 3
docs/reference/commandline/cli.md

@@ -101,7 +101,26 @@ The property `psFormat` specifies the default format for `docker ps` output.
 When the `--format` flag is not provided with the `docker ps` command,
 When the `--format` flag is not provided with the `docker ps` command,
 Docker's client uses this property. If this property is not set, the client
 Docker's client uses this property. If this property is not set, the client
 falls back to the default table format. For a list of supported formatting
 falls back to the default table format. For a list of supported formatting
-directives, see the [**Formatting** section in the `docker ps` documentation](ps.md)
+directives, see the
+[**Formatting** section in the `docker ps` documentation](ps.md)
+
+Once attached to a container, users detach from it and leave it running using
+the using `CTRL-p CTRL-q` key sequence. This detach key sequence is customizable
+using the `detachKeys` property. Specify a `<sequence>` value for the
+property. The format of the `<sequence>` is either a letter [a-Z], or the `ctrl-`
+combined with any of the following:
+
+* `a-z` (a single lowercase alpha character )
+* `@` (ampersand)
+* `[` (left bracket)
+* `\\` (two backward slashes)
+*  `_` (underscore)
+* `^` (caret)
+
+Your customization applies to all containers started in with your Docker client.
+Users can override your custom or the default key sequence on a per-container
+basis. To do this, the user specifies the `--detach-keys` flag with the `docker
+attach`, `docker exec`, `docker run` or `docker start` command.
 
 
 The property `imagesFormat` specifies the default format for `docker images` output.
 The property `imagesFormat` specifies the default format for `docker images` output.
 When the `--format` flag is not provided with the `docker images` command,
 When the `--format` flag is not provided with the `docker images` command,
@@ -115,8 +134,9 @@ Following is a sample `config.json` file:
       "HttpHeaders": {
       "HttpHeaders": {
         "MyHeader": "MyValue"
         "MyHeader": "MyValue"
       },
       },
-      "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}"
-      "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}"
+      "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}",
+      "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}",
+      "detachKeys": "ctrl-e,e"
     }
     }
 
 
 ### Notary
 ### Notary

+ 1 - 0
docs/reference/commandline/exec.md

@@ -15,6 +15,7 @@ parent = "smn_cli"
     Run a command in a running container
     Run a command in a running container
 
 
       -d, --detach               Detached mode: run command in the background
       -d, --detach               Detached mode: run command in the background
+      --detach-keys              Specify the escape key sequence used to detach a container
       --help                     Print usage
       --help                     Print usage
       -i, --interactive          Keep STDIN open even if not attached
       -i, --interactive          Keep STDIN open even if not attached
       --privileged               Give extended Linux capabilities to the command
       --privileged               Give extended Linux capabilities to the command

+ 1 - 0
docs/reference/commandline/run.md

@@ -28,6 +28,7 @@ parent = "smn_cli"
       --cpuset-cpus=""              CPUs in which to allow execution (0-3, 0,1)
       --cpuset-cpus=""              CPUs in which to allow execution (0-3, 0,1)
       --cpuset-mems=""              Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
       --cpuset-mems=""              Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
       -d, --detach                  Run container in background and print container ID
       -d, --detach                  Run container in background and print container ID
+      --detach-keys                 Specify the escape key sequence used to detach a container
       --device=[]                   Add a host device to the container
       --device=[]                   Add a host device to the container
       --device-read-bps=[]          Limit read rate (bytes per second) from a device (e.g., --device-read-bps=/dev/sda:1mb)
       --device-read-bps=[]          Limit read rate (bytes per second) from a device (e.g., --device-read-bps=/dev/sda:1mb)
       --device-read-iops=[]         Limit read rate (IO per second) from a device (e.g., --device-read-iops=/dev/sda:1000)
       --device-read-iops=[]         Limit read rate (IO per second) from a device (e.g., --device-read-iops=/dev/sda:1000)

+ 1 - 0
docs/reference/commandline/start.md

@@ -15,5 +15,6 @@ parent = "smn_cli"
     Start one or more containers
     Start one or more containers
 
 
       -a, --attach               Attach STDOUT/STDERR and forward signals
       -a, --attach               Attach STDOUT/STDERR and forward signals
+      --detach-keys              Specify the escape key sequence used to detach a container
       --help                     Print usage
       --help                     Print usage
       -i, --interactive          Attach container's STDIN
       -i, --interactive          Attach container's STDIN

+ 235 - 4
integration-cli/docker_cli_run_unix_test.go

@@ -14,6 +14,7 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
+	"github.com/docker/docker/pkg/homedir"
 	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/parsers"
 	"github.com/docker/docker/pkg/parsers"
@@ -87,10 +88,13 @@ func (s *DockerSuite) TestRunDeviceDirectory(c *check.C) {
 	c.Assert(strings.Trim(out, "\r\n"), checker.Contains, "seq", check.Commentf("expected output /dev/othersnd/seq"))
 	c.Assert(strings.Trim(out, "\r\n"), checker.Contains, "seq", check.Commentf("expected output /dev/othersnd/seq"))
 }
 }
 
 
-// TestRunDetach checks attaching and detaching with the escape sequence.
+// TestRunDetach checks attaching and detaching with the default escape sequence.
 func (s *DockerSuite) TestRunAttachDetach(c *check.C) {
 func (s *DockerSuite) TestRunAttachDetach(c *check.C) {
 	name := "attach-detach"
 	name := "attach-detach"
-	cmd := exec.Command(dockerBinary, "run", "--name", name, "-it", "busybox", "cat")
+
+	dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat")
+
+	cmd := exec.Command(dockerBinary, "attach", name)
 	stdout, err := cmd.StdoutPipe()
 	stdout, err := cmd.StdoutPipe()
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
 	cpty, tty, err := pty.Open()
 	cpty, tty, err := pty.Open()
@@ -120,21 +124,248 @@ func (s *DockerSuite) TestRunAttachDetach(c *check.C) {
 		ch <- struct{}{}
 		ch <- struct{}{}
 	}()
 	}()
 
 
+	select {
+	case <-ch:
+	case <-time.After(10 * time.Second):
+		c.Fatal("timed out waiting for container to exit")
+	}
+
 	running, err := inspectField(name, "State.Running")
 	running, err := inspectField(name, "State.Running")
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
 	c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
 	c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
+}
+
+// TestRunDetach checks attaching and detaching with the escape sequence specified via flags.
+func (s *DockerSuite) TestRunAttachDetachFromFlag(c *check.C) {
+	name := "attach-detach"
+	keyCtrlA := []byte{1}
+	keyA := []byte{97}
+
+	dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat")
+
+	cmd := exec.Command(dockerBinary, "attach", "--detach-keys='ctrl-a,a'", name)
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		c.Fatal(err)
+	}
+	cpty, tty, err := pty.Open()
+	if err != nil {
+		c.Fatal(err)
+	}
+	defer cpty.Close()
+	cmd.Stdin = tty
+	if err := cmd.Start(); err != nil {
+		c.Fatal(err)
+	}
+	c.Assert(waitRun(name), check.IsNil)
+
+	if _, err := cpty.Write([]byte("hello\n")); err != nil {
+		c.Fatal(err)
+	}
+
+	out, err := bufio.NewReader(stdout).ReadString('\n')
+	if err != nil {
+		c.Fatal(err)
+	}
+	if strings.TrimSpace(out) != "hello" {
+		c.Fatalf("expected 'hello', got %q", out)
+	}
+
+	// escape sequence
+	if _, err := cpty.Write(keyCtrlA); err != nil {
+		c.Fatal(err)
+	}
+	time.Sleep(100 * time.Millisecond)
+	if _, err := cpty.Write(keyA); err != nil {
+		c.Fatal(err)
+	}
 
 
+	ch := make(chan struct{})
 	go func() {
 	go func() {
-		exec.Command(dockerBinary, "kill", name).Run()
+		cmd.Wait()
+		ch <- struct{}{}
 	}()
 	}()
 
 
 	select {
 	select {
 	case <-ch:
 	case <-ch:
-	case <-time.After(10 * time.Millisecond):
+	case <-time.After(10 * time.Second):
 		c.Fatal("timed out waiting for container to exit")
 		c.Fatal("timed out waiting for container to exit")
 	}
 	}
+
+	running, err := inspectField(name, "State.Running")
+	c.Assert(err, checker.IsNil)
+	c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
 }
 }
 
 
+// TestRunDetach checks attaching and detaching with the escape sequence specified via config file.
+func (s *DockerSuite) TestRunAttachDetachFromConfig(c *check.C) {
+	keyCtrlA := []byte{1}
+	keyA := []byte{97}
+
+	// Setup config
+	homeKey := homedir.Key()
+	homeVal := homedir.Get()
+	tmpDir, err := ioutil.TempDir("", "fake-home")
+	c.Assert(err, checker.IsNil)
+	defer os.RemoveAll(tmpDir)
+
+	dotDocker := filepath.Join(tmpDir, ".docker")
+	os.Mkdir(dotDocker, 0600)
+	tmpCfg := filepath.Join(dotDocker, "config.json")
+
+	defer func() { os.Setenv(homeKey, homeVal) }()
+	os.Setenv(homeKey, tmpDir)
+
+	data := `{
+		"detachKeys": "ctrl-a,a"
+	}`
+
+	err = ioutil.WriteFile(tmpCfg, []byte(data), 0600)
+	c.Assert(err, checker.IsNil)
+
+	// Then do the work
+	name := "attach-detach"
+	dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat")
+
+	cmd := exec.Command(dockerBinary, "attach", name)
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		c.Fatal(err)
+	}
+	cpty, tty, err := pty.Open()
+	if err != nil {
+		c.Fatal(err)
+	}
+	defer cpty.Close()
+	cmd.Stdin = tty
+	if err := cmd.Start(); err != nil {
+		c.Fatal(err)
+	}
+	c.Assert(waitRun(name), check.IsNil)
+
+	if _, err := cpty.Write([]byte("hello\n")); err != nil {
+		c.Fatal(err)
+	}
+
+	out, err := bufio.NewReader(stdout).ReadString('\n')
+	if err != nil {
+		c.Fatal(err)
+	}
+	if strings.TrimSpace(out) != "hello" {
+		c.Fatalf("expected 'hello', got %q", out)
+	}
+
+	// escape sequence
+	if _, err := cpty.Write(keyCtrlA); err != nil {
+		c.Fatal(err)
+	}
+	time.Sleep(100 * time.Millisecond)
+	if _, err := cpty.Write(keyA); err != nil {
+		c.Fatal(err)
+	}
+
+	ch := make(chan struct{})
+	go func() {
+		cmd.Wait()
+		ch <- struct{}{}
+	}()
+
+	select {
+	case <-ch:
+	case <-time.After(10 * time.Second):
+		c.Fatal("timed out waiting for container to exit")
+	}
+
+	running, err := inspectField(name, "State.Running")
+	c.Assert(err, checker.IsNil)
+	c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
+}
+
+// TestRunDetach checks attaching and detaching with the detach flags, making sure it overrides config file
+func (s *DockerSuite) TestRunAttachDetachKeysOverrideConfig(c *check.C) {
+	keyCtrlA := []byte{1}
+	keyA := []byte{97}
+
+	// Setup config
+	homeKey := homedir.Key()
+	homeVal := homedir.Get()
+	tmpDir, err := ioutil.TempDir("", "fake-home")
+	c.Assert(err, checker.IsNil)
+	defer os.RemoveAll(tmpDir)
+
+	dotDocker := filepath.Join(tmpDir, ".docker")
+	os.Mkdir(dotDocker, 0600)
+	tmpCfg := filepath.Join(dotDocker, "config.json")
+
+	defer func() { os.Setenv(homeKey, homeVal) }()
+	os.Setenv(homeKey, tmpDir)
+
+	data := `{
+		"detachKeys": "ctrl-e,e"
+	}`
+
+	err = ioutil.WriteFile(tmpCfg, []byte(data), 0600)
+	c.Assert(err, checker.IsNil)
+
+	// Then do the work
+	name := "attach-detach"
+	dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat")
+
+	cmd := exec.Command(dockerBinary, "attach", "--detach-keys='ctrl-a,a'", name)
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		c.Fatal(err)
+	}
+	cpty, tty, err := pty.Open()
+	if err != nil {
+		c.Fatal(err)
+	}
+	defer cpty.Close()
+	cmd.Stdin = tty
+	if err := cmd.Start(); err != nil {
+		c.Fatal(err)
+	}
+	c.Assert(waitRun(name), check.IsNil)
+
+	if _, err := cpty.Write([]byte("hello\n")); err != nil {
+		c.Fatal(err)
+	}
+
+	out, err := bufio.NewReader(stdout).ReadString('\n')
+	if err != nil {
+		c.Fatal(err)
+	}
+	if strings.TrimSpace(out) != "hello" {
+		c.Fatalf("expected 'hello', got %q", out)
+	}
+
+	// escape sequence
+	if _, err := cpty.Write(keyCtrlA); err != nil {
+		c.Fatal(err)
+	}
+	time.Sleep(100 * time.Millisecond)
+	if _, err := cpty.Write(keyA); err != nil {
+		c.Fatal(err)
+	}
+
+	ch := make(chan struct{})
+	go func() {
+		cmd.Wait()
+		ch <- struct{}{}
+	}()
+
+	select {
+	case <-ch:
+	case <-time.After(10 * time.Second):
+		c.Fatal("timed out waiting for container to exit")
+	}
+
+	running, err := inspectField(name, "State.Running")
+	c.Assert(err, checker.IsNil)
+	c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
+}
+
+// "test" should be printed
 func (s *DockerSuite) TestRunWithCPUQuota(c *check.C) {
 func (s *DockerSuite) TestRunWithCPUQuota(c *check.C) {
 	testRequires(c, cpuCfsQuota)
 	testRequires(c, cpuCfsQuota)
 
 

+ 33 - 4
man/docker-attach.1.md

@@ -6,6 +6,7 @@ docker-attach - Attach to a running container
 
 
 # SYNOPSIS
 # SYNOPSIS
 **docker attach**
 **docker attach**
+[**--detach-keys**[=*[]*]]
 [**--help**]
 [**--help**]
 [**--no-stdin**]
 [**--no-stdin**]
 [**--sig-proxy**[=*true*]]
 [**--sig-proxy**[=*true*]]
@@ -18,15 +19,19 @@ interactively.  You can attach to the same contained process multiple times
 simultaneously, screen sharing style, or quickly view the progress of your
 simultaneously, screen sharing style, or quickly view the progress of your
 detached process.
 detached process.
 
 
-You can detach from the container (and leave it running) with `CTRL-p CTRL-q`
-(for a quiet exit) or `CTRL-c` which will send a `SIGKILL` to the container.
-When you are attached to a container, and exit its main process, the process's
-exit code will be returned to the client.
+To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the
+container. You can detach from the container (and leave it running) using a
+configurable key sequence. The default sequence is `CTRL-p CTRL-q`. You
+configure the key sequence using the **--detach-keys** option or a configuration
+file. See **config-json(5)** for documentation on using a configuration file.
 
 
 It is forbidden to redirect the standard input of a `docker attach` command while
 It is forbidden to redirect the standard input of a `docker attach` command while
 attaching to a tty-enabled container (i.e.: launched with `-t`).
 attaching to a tty-enabled container (i.e.: launched with `-t`).
 
 
 # OPTIONS
 # OPTIONS
+**--detach-keys**=""
+    Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
+
 **--help**
 **--help**
   Print usage statement
   Print usage statement
 
 
@@ -36,6 +41,30 @@ attaching to a tty-enabled container (i.e.: launched with `-t`).
 **--sig-proxy**=*true*|*false*
 **--sig-proxy**=*true*|*false*
    Proxy all received signals to the process (non-TTY mode only). SIGCHLD, SIGKILL, and SIGSTOP are not proxied. The default is *true*.
    Proxy all received signals to the process (non-TTY mode only). SIGCHLD, SIGKILL, and SIGSTOP are not proxied. The default is *true*.
 
 
+# Override the detach sequence
+
+If you want, you can configure a override the Docker key sequence for detach.
+This is is useful if the Docker default sequence conflicts with key squence you
+use for other applications. There are two ways to defines a your own detach key
+sequence, as a per-container override or as a configuration property on  your
+entire configuration.
+
+To override the sequence for an individual container, use the
+`--detach-keys="<sequence>"` flag with the `docker attach` command. The format of
+the `<sequence>` is either a letter [a-Z], or the `ctrl-` combined with any of
+the following:
+
+* `a-z` (a single lowercase alpha character )
+* `@` (ampersand)
+* `[` (left bracket)
+* `\\` (two backward slashes)
+*  `_` (underscore)
+* `^` (caret)
+
+These `a`, `ctrl-a`, `X`, or `ctrl-\\` values are all examples of valid key
+sequences. To configure a different configuration default key sequence for all
+containers, see **docker(1)**.
+
 # EXAMPLES
 # EXAMPLES
 
 
 ## Attaching to a container
 ## Attaching to a container

+ 5 - 1
man/docker-exec.1.md

@@ -7,6 +7,7 @@ docker-exec - Run a command in a running container
 # SYNOPSIS
 # SYNOPSIS
 **docker exec**
 **docker exec**
 [**-d**|**--detach**]
 [**-d**|**--detach**]
+[**--detach-keys**[=*[]*]]
 [**--help**]
 [**--help**]
 [**-i**|**--interactive**]
 [**-i**|**--interactive**]
 [**--privileged**]
 [**--privileged**]
@@ -26,7 +27,10 @@ container is unpaused, and then run
 
 
 # OPTIONS
 # OPTIONS
 **-d**, **--detach**=*true*|*false*
 **-d**, **--detach**=*true*|*false*
-   Detached mode: run command in the background. The default is *false*.
+    Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
+
+**--detach-keys**=""
+  Define the key sequence which detaches the container.
 
 
 **--help**
 **--help**
   Print usage statement
   Print usage statement

+ 8 - 2
man/docker-run.1.md

@@ -20,6 +20,7 @@ docker-run - Run a command in a new container
 [**--cpuset-cpus**[=*CPUSET-CPUS*]]
 [**--cpuset-cpus**[=*CPUSET-CPUS*]]
 [**--cpuset-mems**[=*CPUSET-MEMS*]]
 [**--cpuset-mems**[=*CPUSET-MEMS*]]
 [**-d**|**--detach**]
 [**-d**|**--detach**]
+[**--detach-keys**[=*[]*]]
 [**--device**[=*[]*]]
 [**--device**[=*[]*]]
 [**--device-read-bps**[=*[]*]]
 [**--device-read-bps**[=*[]*]]
 [**--device-read-iops**[=*[]*]]
 [**--device-read-iops**[=*[]*]]
@@ -190,8 +191,13 @@ the other shell to view a list of the running containers. You can reattach to a
 detached container with **docker attach**. If you choose to run a container in
 detached container with **docker attach**. If you choose to run a container in
 the detached mode, then you cannot use the **-rm** option.
 the detached mode, then you cannot use the **-rm** option.
 
 
-   When attached in the tty mode, you can detach from a running container without
-stopping the process by pressing the keys CTRL-P CTRL-Q.
+   When attached in the tty mode, you can detach from the container (and leave it
+running) using a configurable key sequence. The default sequence is `CTRL-p CTRL-q`.
+You configure the key sequence using the **--detach-keys** option or a configuration file.
+See **config-json(5)** for documentation on using a configuration file.
+
+**--detach-keys**=""
+   Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
 
 
 **--device**=[]
 **--device**=[]
    Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
    Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)

+ 6 - 1
man/docker-start.1.md

@@ -7,6 +7,7 @@ docker-start - Start one or more containers
 # SYNOPSIS
 # SYNOPSIS
 **docker start**
 **docker start**
 [**-a**|**--attach**]
 [**-a**|**--attach**]
+[**--detach-keys**[=*[]*]]
 [**--help**]
 [**--help**]
 [**-i**|**--interactive**]
 [**-i**|**--interactive**]
 CONTAINER [CONTAINER...]
 CONTAINER [CONTAINER...]
@@ -17,7 +18,11 @@ Start one or more containers.
 
 
 # OPTIONS
 # OPTIONS
 **-a**, **--attach**=*true*|*false*
 **-a**, **--attach**=*true*|*false*
-   Attach container's STDOUT and STDERR and forward all signals to the process. The default is *false*.
+   Attach container's STDOUT and STDERR and forward all signals to the
+   process. The default is *false*.
+
+**--detach-keys**=""
+   Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
 
 
 **--help**
 **--help**
   Print usage statement
   Print usage statement

+ 1 - 0
man/docker.1.md

@@ -223,6 +223,7 @@ inside it)
   Block until a container stops, then print its exit code
   Block until a container stops, then print its exit code
   See **docker-wait(1)** for full documentation on the **wait** command.
   See **docker-wait(1)** for full documentation on the **wait** command.
 
 
+
 # EXEC DRIVER OPTIONS
 # EXEC DRIVER OPTIONS
 
 
 Use the **--exec-opt** flags to specify options to the execution driver. The only
 Use the **--exec-opt** flags to specify options to the execution driver. The only

+ 66 - 0
pkg/term/ascii.go

@@ -0,0 +1,66 @@
+package term
+
+import (
+	"fmt"
+	"strings"
+)
+
+// ASCII list the possible supported ASCII key sequence
+var ASCII = []string{
+	"ctrl-@",
+	"ctrl-a",
+	"ctrl-b",
+	"ctrl-c",
+	"ctrl-d",
+	"ctrl-e",
+	"ctrl-f",
+	"ctrl-g",
+	"ctrl-h",
+	"ctrl-i",
+	"ctrl-j",
+	"ctrl-k",
+	"ctrl-l",
+	"ctrl-m",
+	"ctrl-n",
+	"ctrl-o",
+	"ctrl-p",
+	"ctrl-q",
+	"ctrl-r",
+	"ctrl-s",
+	"ctrl-t",
+	"ctrl-u",
+	"ctrl-v",
+	"ctrl-w",
+	"ctrl-x",
+	"ctrl-y",
+	"ctrl-z",
+	"ctrl-[",
+	"ctrl-\\",
+	"ctrl-]",
+	"ctrl-^",
+	"ctrl-_",
+}
+
+// ToBytes converts a string representing a suite of key-sequence to the corresponding ASCII code.
+func ToBytes(keys string) ([]byte, error) {
+	codes := []byte{}
+next:
+	for _, key := range strings.Split(keys, ",") {
+		if len(key) != 1 {
+			for code, ctrl := range ASCII {
+				if ctrl == key {
+					codes = append(codes, byte(code))
+					continue next
+				}
+			}
+			if key == "DEL" {
+				codes = append(codes, 127)
+			} else {
+				return nil, fmt.Errorf("Unknown character: '%s'", key)
+			}
+		} else {
+			codes = append(codes, byte(key[0]))
+		}
+	}
+	return codes, nil
+}

+ 43 - 0
pkg/term/ascii_test.go

@@ -0,0 +1,43 @@
+package term
+
+import "testing"
+
+func TestToBytes(t *testing.T) {
+	codes, err := ToBytes("ctrl-a,a")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(codes) != 2 {
+		t.Fatalf("Expected 2 codes, got %d", len(codes))
+	}
+	if codes[0] != 1 || codes[1] != 97 {
+		t.Fatalf("Expected '1' '97', got '%d' '%d'", codes[0], codes[1])
+	}
+
+	codes, err = ToBytes("shift-z")
+	if err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+
+	codes, err = ToBytes("ctrl-@,ctrl-[,~,ctrl-o")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(codes) != 4 {
+		t.Fatalf("Expected 4 codes, got %d", len(codes))
+	}
+	if codes[0] != 0 || codes[1] != 27 || codes[2] != 126 || codes[3] != 15 {
+		t.Fatalf("Expected '0' '27' '126', '15', got '%d' '%d' '%d' '%d'", codes[0], codes[1], codes[2], codes[3])
+	}
+
+	codes, err = ToBytes("DEL,+")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(codes) != 2 {
+		t.Fatalf("Expected 2 codes, got %d", len(codes))
+	}
+	if codes[0] != 127 || codes[1] != 43 {
+		t.Fatalf("Expected '127 '43'', got '%d' '%d'", codes[0], codes[1])
+	}
+}