From 01ce312c2d3e56a7993f7d644675513e1acca17c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 26 Jul 2013 17:40:45 -0700 Subject: [PATCH 01/23] Exit from `docker login` on SIGTERM and SIGINT. Fixes #1299. --- commands.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/commands.go b/commands.go index 0fabaa385f..1fb56e98a6 100644 --- a/commands.go +++ b/commands.go @@ -319,6 +319,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig = auth.AuthConfig{} } + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + go func() { + for sig := range c { + os.Exit(1) + } + }() + if *flUsername == "" { fmt.Fprintf(cli.out, "Username (%s): ", authconfig.Username) username = readAndEchoString(cli.in, cli.out) From 7cc90f2bc552f5b3b49e65e19ac877089e3db137 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 26 Jul 2013 18:12:05 -0700 Subject: [PATCH 02/23] Use a more idiomatic syntax to capture the exit. --- commands.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/commands.go b/commands.go index 1fb56e98a6..d2764086ef 100644 --- a/commands.go +++ b/commands.go @@ -319,12 +319,11 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig = auth.AuthConfig{} } - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) go func() { - for sig := range c { - os.Exit(1) - } + <-sigchan + os.Exit(1) }() if *flUsername == "" { From bb06fe8dd9b84a196eae19e4c955bb2e5a09dba2 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 27 Jul 2013 09:13:02 -0700 Subject: [PATCH 03/23] Allow to generate signals when termios is in raw mode. --- commands.go | 7 ------- term/termios_darwin.go | 2 +- term/termios_linux.go | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/commands.go b/commands.go index d2764086ef..0fabaa385f 100644 --- a/commands.go +++ b/commands.go @@ -319,13 +319,6 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig = auth.AuthConfig{} } - sigchan := make(chan os.Signal, 1) - signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) - go func() { - <-sigchan - os.Exit(1) - }() - if *flUsername == "" { fmt.Fprintf(cli.out, "Username (%s): ", authconfig.Username) username = readAndEchoString(cli.in, cli.out) diff --git a/term/termios_darwin.go b/term/termios_darwin.go index 24e79de4b2..0f6b24b184 100644 --- a/term/termios_darwin.go +++ b/term/termios_darwin.go @@ -44,7 +44,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (ISTRIP | INLCR | IGNCR | IXON | IXOFF) newState.Iflag |= ICRNL newState.Oflag |= ONLCR - newState.Lflag &^= (ECHO | ICANON | ISIG) + newState.Lflag &^= (ECHO | ICANON) if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { return nil, err diff --git a/term/termios_linux.go b/term/termios_linux.go index 4a717c84a7..22f4fff430 100644 --- a/term/termios_linux.go +++ b/term/termios_linux.go @@ -33,7 +33,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN) + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.IEXTEN) newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) newState.Cflag |= syscall.CS8 From 75ac50a9a0669b464a95e069e66da5d47df9182f Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 16:43:20 -0700 Subject: [PATCH 04/23] Stop making a raw terminal to ask for registry login credentials. It only disables echo asking for the password and lets the terminal to handle everything else. It fixes #1392 since blank spaces are not discarded as they did before. It also cleans the login code a little bit to improve readability. --- commands.go | 111 +++++++++++++++------------------------------------ term/term.go | 44 ++++++++++++++++---- 2 files changed, 68 insertions(+), 87 deletions(-) diff --git a/commands.go b/commands.go index a2099b9607..416076d3d5 100644 --- a/commands.go +++ b/commands.go @@ -2,6 +2,7 @@ package docker import ( "archive/tar" + "bufio" "bytes" "encoding/json" "flag" @@ -24,7 +25,6 @@ import ( "syscall" "text/tabwriter" "time" - "unicode" ) const VERSION = "0.5.0-dev" @@ -253,73 +253,18 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // 'docker login': login / register a user to registry service. func (cli *DockerCli) CmdLogin(args ...string) error { - var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string { - char := make([]byte, 1) - buffer := make([]byte, 64) - var i = 0 - for i < len(buffer) { - n, err := stdin.Read(char) - if n > 0 { - if char[0] == '\r' || char[0] == '\n' { - stdout.Write([]byte{'\r', '\n'}) - break - } else if char[0] == 127 || char[0] == '\b' { - if i > 0 { - if echo { - stdout.Write([]byte{'\b', ' ', '\b'}) - } - i-- - } - } else if !unicode.IsSpace(rune(char[0])) && - !unicode.IsControl(rune(char[0])) { - if echo { - stdout.Write(char) - } - buffer[i] = char[0] - i++ - } - } - if err != nil { - if err != io.EOF { - fmt.Fprintf(stdout, "Read error: %v\r\n", err) - } - break - } - } - return string(buffer[:i]) - } - var readAndEchoString = func(stdin io.Reader, stdout io.Writer) string { - return readStringOnRawTerminal(stdin, stdout, true) - } - var readString = func(stdin io.Reader, stdout io.Writer) string { - return readStringOnRawTerminal(stdin, stdout, false) - } - cmd := Subcmd("login", "[OPTIONS]", "Register or Login to the docker registry server") - flUsername := cmd.String("u", "", "username") - flPassword := cmd.String("p", "", "password") - flEmail := cmd.String("e", "", "email") + + username := *cmd.String("u", "", "username") + password := *cmd.String("p", "", "password") + email := *cmd.String("e", "", "email") err := cmd.Parse(args) + if err != nil { return nil } - var oldState *term.State - if *flUsername == "" || *flPassword == "" || *flEmail == "" { - oldState, err = term.SetRawTerminal(cli.terminalFd) - if err != nil { - return err - } - defer term.RestoreTerminal(cli.terminalFd, oldState) - } - - var ( - username string - password string - email string - ) - - var promptDefault = func(prompt string, configDefault string) { + promptDefault := func(prompt string, configDefault string) { if configDefault == "" { fmt.Fprintf(cli.out, "%s: ", prompt) } else { @@ -327,47 +272,55 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } } + readInput := func(in io.Reader) (string, error) { + reader := bufio.NewReader(in) + line, err := reader.ReadString('\n') + if err != nil { + return "", err + } + return line, nil + } + authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] if !ok { authconfig = auth.AuthConfig{} } - if *flUsername == "" { + if username == "" { promptDefault("Username", authconfig.Username) - username = readAndEchoString(cli.in, cli.out) + username, _ = readInput(cli.in) if username == "" { username = authconfig.Username } - } else { - username = *flUsername } + if username != authconfig.Username { - if *flPassword == "" { + if password == "" { + oldState, _ := term.SaveState(cli.terminalFd) fmt.Fprintf(cli.out, "Password: ") - password = readString(cli.in, cli.out) + + term.DisableEcho(cli.terminalFd, cli.out, oldState) + password, _ = readInput(cli.in) + + term.RestoreTerminal(cli.terminalFd, oldState) + if password == "" { return fmt.Errorf("Error : Password Required") } - } else { - password = *flPassword } - if *flEmail == "" { - promptDefault("Email", authconfig.Email) - email = readAndEchoString(cli.in, cli.out) + if email == "" { + promptDefault("\nEmail", authconfig.Email) + email, _ = readInput(cli.in) if email == "" { email = authconfig.Email } - } else { - email = *flEmail } } else { password = authconfig.Password email = authconfig.Email } - if oldState != nil { - term.RestoreTerminal(cli.terminalFd, oldState) - } + authconfig.Username = username authconfig.Password = password authconfig.Email = email @@ -1620,7 +1573,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea }) if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" { - oldState, err := term.SetRawTerminal(cli.terminalFd) + oldState, err := term.SetRawTerminal(cli.terminalFd, cli.out) if err != nil { return err } diff --git a/term/term.go b/term/term.go index f4d66a71d6..074319c287 100644 --- a/term/term.go +++ b/term/term.go @@ -1,6 +1,8 @@ package term import ( + "fmt" + "io" "os" "os/signal" "syscall" @@ -43,17 +45,43 @@ func RestoreTerminal(fd uintptr, state *State) error { return err } -func SetRawTerminal(fd uintptr) (*State, error) { +func SaveState(fd uintptr) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 { + return nil, err + } + + return &oldState, nil +} + +func DisableEcho(fd uintptr, out io.Writer, state *State) error { + newState := state.termios + newState.Lflag &^= syscall.ECHO + + HandleInterrupt(fd, out, state) + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 { + return err + } + return nil +} + +func HandleInterrupt(fd uintptr, out io.Writer, state *State) { + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, os.Interrupt) + + go func() { + _ = <-sigchan + fmt.Fprintf(out, "\n") + RestoreTerminal(fd, state) + os.Exit(0) + }() +} + +func SetRawTerminal(fd uintptr, out io.Writer) (*State, error) { oldState, err := MakeRaw(fd) if err != nil { return nil, err } - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - go func() { - _ = <-c - RestoreTerminal(fd, oldState) - os.Exit(0) - }() + HandleInterrupt(fd, out, oldState) return oldState, err } From 4089a20cf476d2241b051d9ba913b7adde90661d Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 17:27:15 -0700 Subject: [PATCH 05/23] Exit if there is any error reading from stdin. --- commands.go | 13 +++++++------ term/term.go | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/commands.go b/commands.go index 416076d3d5..51efacd09b 100644 --- a/commands.go +++ b/commands.go @@ -272,13 +272,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } } - readInput := func(in io.Reader) (string, error) { + readInput := func(in io.Reader, out io.Writer) string { reader := bufio.NewReader(in) line, err := reader.ReadString('\n') if err != nil { - return "", err + fmt.Fprintln(out, err.Error()) + os.Exit(1) } - return line, nil + return line } authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] @@ -288,7 +289,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if username == "" { promptDefault("Username", authconfig.Username) - username, _ = readInput(cli.in) + username = readInput(cli.in, cli.out) if username == "" { username = authconfig.Username } @@ -300,7 +301,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { fmt.Fprintf(cli.out, "Password: ") term.DisableEcho(cli.terminalFd, cli.out, oldState) - password, _ = readInput(cli.in) + password = readInput(cli.in, cli.out) term.RestoreTerminal(cli.terminalFd, oldState) @@ -311,7 +312,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if email == "" { promptDefault("\nEmail", authconfig.Email) - email, _ = readInput(cli.in) + email = readInput(cli.in, cli.out) if email == "" { email = authconfig.Email } diff --git a/term/term.go b/term/term.go index 074319c287..2c78d6806e 100644 --- a/term/term.go +++ b/term/term.go @@ -71,7 +71,7 @@ func HandleInterrupt(fd uintptr, out io.Writer, state *State) { go func() { _ = <-sigchan - fmt.Fprintf(out, "\n") + fmt.Fprint(out, "\n") RestoreTerminal(fd, state) os.Exit(0) }() From bdaa87ff2158781c0772e1cab26b52ae6ec07ce4 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 21:30:07 -0700 Subject: [PATCH 06/23] Print a new line after getting the password from stdin. --- commands.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 51efacd09b..814084f353 100644 --- a/commands.go +++ b/commands.go @@ -302,6 +302,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { term.DisableEcho(cli.terminalFd, cli.out, oldState) password = readInput(cli.in, cli.out) + fmt.Fprint(cli.out, "\n") term.RestoreTerminal(cli.terminalFd, oldState) @@ -311,7 +312,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } if email == "" { - promptDefault("\nEmail", authconfig.Email) + promptDefault("Email", authconfig.Email) email = readInput(cli.in, cli.out) if email == "" { email = authconfig.Email From 8a18999d2352d9c93e44935222e24874476c5018 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 21:08:00 -0700 Subject: [PATCH 07/23] Add the ISIG syscall back to not kill the client withing a shell with ctrl+c. --- term/termios_darwin.go | 2 +- term/termios_linux.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/term/termios_darwin.go b/term/termios_darwin.go index 0f6b24b184..24e79de4b2 100644 --- a/term/termios_darwin.go +++ b/term/termios_darwin.go @@ -44,7 +44,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (ISTRIP | INLCR | IGNCR | IXON | IXOFF) newState.Iflag |= ICRNL newState.Oflag |= ONLCR - newState.Lflag &^= (ECHO | ICANON) + newState.Lflag &^= (ECHO | ICANON | ISIG) if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { return nil, err diff --git a/term/termios_linux.go b/term/termios_linux.go index 22f4fff430..6a76460a54 100644 --- a/term/termios_linux.go +++ b/term/termios_linux.go @@ -33,7 +33,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.IEXTEN) + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.EXTEN) newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) newState.Cflag |= syscall.CS8 From 45543d012e98de5ed9b1e415c8ce417fe02d3c55 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 21:29:37 -0700 Subject: [PATCH 08/23] Simplify term signal handler. --- commands.go | 5 +++-- term/term.go | 25 ++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/commands.go b/commands.go index 814084f353..9300758a34 100644 --- a/commands.go +++ b/commands.go @@ -300,7 +300,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error { oldState, _ := term.SaveState(cli.terminalFd) fmt.Fprintf(cli.out, "Password: ") - term.DisableEcho(cli.terminalFd, cli.out, oldState) + term.DisableEcho(cli.terminalFd, oldState) + password = readInput(cli.in, cli.out) fmt.Fprint(cli.out, "\n") @@ -1575,7 +1576,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea }) if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" { - oldState, err := term.SetRawTerminal(cli.terminalFd, cli.out) + oldState, err := term.SetRawTerminal(cli.terminalFd) if err != nil { return err } diff --git a/term/term.go b/term/term.go index 2c78d6806e..d8d4d1a655 100644 --- a/term/term.go +++ b/term/term.go @@ -54,34 +54,33 @@ func SaveState(fd uintptr) (*State, error) { return &oldState, nil } -func DisableEcho(fd uintptr, out io.Writer, state *State) error { +func DisableEcho(fd uintptr, state *State) error { newState := state.termios newState.Lflag &^= syscall.ECHO - HandleInterrupt(fd, out, state) if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 { return err } + handleInterrupt(fd, state) return nil } -func HandleInterrupt(fd uintptr, out io.Writer, state *State) { +func SetRawTerminal(fd uintptr) (*State, error) { + oldState, err := MakeRaw(fd) + if err != nil { + return nil, err + } + handleInterrupt(fd, oldState) + return oldState, err +} + +func handleInterrupt(fd uintptr, state *State) { sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, os.Interrupt) go func() { _ = <-sigchan - fmt.Fprint(out, "\n") RestoreTerminal(fd, state) os.Exit(0) }() } - -func SetRawTerminal(fd uintptr, out io.Writer) (*State, error) { - oldState, err := MakeRaw(fd) - if err != nil { - return nil, err - } - HandleInterrupt(fd, out, oldState) - return oldState, err -} From 276d2bbf1d400415bec8d4652ac61e570b9206e3 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 22:22:11 -0700 Subject: [PATCH 09/23] Remove unused imports. --- term/term.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/term/term.go b/term/term.go index d8d4d1a655..5929c2caa1 100644 --- a/term/term.go +++ b/term/term.go @@ -1,8 +1,6 @@ package term import ( - "fmt" - "io" "os" "os/signal" "syscall" From e69f7142190451eb1e0553e2dfab8e916dc9cead Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 22:22:24 -0700 Subject: [PATCH 10/23] Fix syscall name. --- term/termios_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/term/termios_linux.go b/term/termios_linux.go index 6a76460a54..4a717c84a7 100644 --- a/term/termios_linux.go +++ b/term/termios_linux.go @@ -33,7 +33,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.EXTEN) + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN) newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) newState.Cflag |= syscall.CS8 From 6aff117164a8e665aa9e417ab019581297336d2e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 22:28:05 -0700 Subject: [PATCH 11/23] Use flag.StringVar to capture the command line flags. --- commands.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index 9300758a34..ee71e5fa77 100644 --- a/commands.go +++ b/commands.go @@ -255,9 +255,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error { func (cli *DockerCli) CmdLogin(args ...string) error { cmd := Subcmd("login", "[OPTIONS]", "Register or Login to the docker registry server") - username := *cmd.String("u", "", "username") - password := *cmd.String("p", "", "password") - email := *cmd.String("e", "", "email") + var username, password, email string + + cmd.StringVar(&username, "u", "", "username") + cmd.StringVar(&password, "p", "", "password") + cmd.StringVar(&email, "e", "", "email") err := cmd.Parse(args) if err != nil { From f1d0625cf895abd1e6b22db3470216b9b9c5ef29 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 26 Jul 2013 17:40:45 -0700 Subject: [PATCH 12/23] Exit from `docker login` on SIGTERM and SIGINT. Fixes #1299. --- commands.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/commands.go b/commands.go index 236fb65d10..53f6706ae4 100644 --- a/commands.go +++ b/commands.go @@ -333,6 +333,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig = auth.AuthConfig{} } + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + go func() { + for sig := range c { + os.Exit(1) + } + }() + if *flUsername == "" { promptDefault("Username", authconfig.Username) username = readAndEchoString(cli.in, cli.out) From c3154fdf4d15aed049a9c18a36cc8511ed7e82c1 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 26 Jul 2013 18:12:05 -0700 Subject: [PATCH 13/23] Use a more idiomatic syntax to capture the exit. --- commands.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/commands.go b/commands.go index 53f6706ae4..e078a97fd1 100644 --- a/commands.go +++ b/commands.go @@ -333,12 +333,11 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig = auth.AuthConfig{} } - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) go func() { - for sig := range c { - os.Exit(1) - } + <-sigchan + os.Exit(1) }() if *flUsername == "" { From 23dc52f52804219bd57b34217d8778bda0444f13 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 27 Jul 2013 09:13:02 -0700 Subject: [PATCH 14/23] Allow to generate signals when termios is in raw mode. --- commands.go | 7 ------- term/termios_darwin.go | 2 +- term/termios_linux.go | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/commands.go b/commands.go index e078a97fd1..236fb65d10 100644 --- a/commands.go +++ b/commands.go @@ -333,13 +333,6 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig = auth.AuthConfig{} } - sigchan := make(chan os.Signal, 1) - signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) - go func() { - <-sigchan - os.Exit(1) - }() - if *flUsername == "" { promptDefault("Username", authconfig.Username) username = readAndEchoString(cli.in, cli.out) diff --git a/term/termios_darwin.go b/term/termios_darwin.go index 24e79de4b2..0f6b24b184 100644 --- a/term/termios_darwin.go +++ b/term/termios_darwin.go @@ -44,7 +44,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (ISTRIP | INLCR | IGNCR | IXON | IXOFF) newState.Iflag |= ICRNL newState.Oflag |= ONLCR - newState.Lflag &^= (ECHO | ICANON | ISIG) + newState.Lflag &^= (ECHO | ICANON) if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { return nil, err diff --git a/term/termios_linux.go b/term/termios_linux.go index 4a717c84a7..22f4fff430 100644 --- a/term/termios_linux.go +++ b/term/termios_linux.go @@ -33,7 +33,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN) + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.IEXTEN) newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) newState.Cflag |= syscall.CS8 From 2357fecc92c57e2fcd4a37c60d713508210358f7 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 16:43:20 -0700 Subject: [PATCH 15/23] Stop making a raw terminal to ask for registry login credentials. It only disables echo asking for the password and lets the terminal to handle everything else. It fixes #1392 since blank spaces are not discarded as they did before. It also cleans the login code a little bit to improve readability. --- commands.go | 113 +++++++++++++++------------------------------------ term/term.go | 44 ++++++++++++++++---- 2 files changed, 68 insertions(+), 89 deletions(-) diff --git a/commands.go b/commands.go index 236fb65d10..0643bc8d58 100644 --- a/commands.go +++ b/commands.go @@ -2,6 +2,7 @@ package docker import ( "archive/tar" + "bufio" "bytes" "encoding/json" "flag" @@ -25,7 +26,6 @@ import ( "syscall" "text/tabwriter" "time" - "unicode" ) var ( @@ -252,75 +252,18 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // 'docker login': login / register a user to registry service. func (cli *DockerCli) CmdLogin(args ...string) error { - var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string { - char := make([]byte, 1) - buffer := make([]byte, 64) - var i = 0 - for i < len(buffer) { - n, err := stdin.Read(char) - if n > 0 { - if char[0] == '\r' || char[0] == '\n' { - stdout.Write([]byte{'\r', '\n'}) - break - } else if char[0] == 127 || char[0] == '\b' { - if i > 0 { - if echo { - stdout.Write([]byte{'\b', ' ', '\b'}) - } - i-- - } - } else if !unicode.IsSpace(rune(char[0])) && - !unicode.IsControl(rune(char[0])) { - if echo { - stdout.Write(char) - } - buffer[i] = char[0] - i++ - } - } - if err != nil { - if err != io.EOF { - fmt.Fprintf(stdout, "Read error: %v\r\n", err) - } - break - } - } - return string(buffer[:i]) - } - var readAndEchoString = func(stdin io.Reader, stdout io.Writer) string { - return readStringOnRawTerminal(stdin, stdout, true) - } - var readString = func(stdin io.Reader, stdout io.Writer) string { - return readStringOnRawTerminal(stdin, stdout, false) - } - cmd := Subcmd("login", "[OPTIONS]", "Register or Login to the docker registry server") - flUsername := cmd.String("u", "", "username") - flPassword := cmd.String("p", "", "password") - flEmail := cmd.String("e", "", "email") + + username := *cmd.String("u", "", "username") + password := *cmd.String("p", "", "password") + email := *cmd.String("e", "", "email") err := cmd.Parse(args) + if err != nil { return nil } - cli.LoadConfigFile() - - var oldState *term.State - if *flUsername == "" || *flPassword == "" || *flEmail == "" { - oldState, err = term.SetRawTerminal(cli.terminalFd) - if err != nil { - return err - } - defer term.RestoreTerminal(cli.terminalFd, oldState) - } - - var ( - username string - password string - email string - ) - - var promptDefault = func(prompt string, configDefault string) { + promptDefault := func(prompt string, configDefault string) { if configDefault == "" { fmt.Fprintf(cli.out, "%s: ", prompt) } else { @@ -328,47 +271,55 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } } + readInput := func(in io.Reader) (string, error) { + reader := bufio.NewReader(in) + line, err := reader.ReadString('\n') + if err != nil { + return "", err + } + return line, nil + } + authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] if !ok { authconfig = auth.AuthConfig{} } - if *flUsername == "" { + if username == "" { promptDefault("Username", authconfig.Username) - username = readAndEchoString(cli.in, cli.out) + username, _ = readInput(cli.in) if username == "" { username = authconfig.Username } - } else { - username = *flUsername } + if username != authconfig.Username { - if *flPassword == "" { + if password == "" { + oldState, _ := term.SaveState(cli.terminalFd) fmt.Fprintf(cli.out, "Password: ") - password = readString(cli.in, cli.out) + + term.DisableEcho(cli.terminalFd, cli.out, oldState) + password, _ = readInput(cli.in) + + term.RestoreTerminal(cli.terminalFd, oldState) + if password == "" { return fmt.Errorf("Error : Password Required") } - } else { - password = *flPassword } - if *flEmail == "" { - promptDefault("Email", authconfig.Email) - email = readAndEchoString(cli.in, cli.out) + if email == "" { + promptDefault("\nEmail", authconfig.Email) + email, _ = readInput(cli.in) if email == "" { email = authconfig.Email } - } else { - email = *flEmail } } else { password = authconfig.Password email = authconfig.Email } - if oldState != nil { - term.RestoreTerminal(cli.terminalFd, oldState) - } + authconfig.Username = username authconfig.Password = password authconfig.Email = email @@ -1694,7 +1645,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea } if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" { - oldState, err := term.SetRawTerminal(cli.terminalFd) + oldState, err := term.SetRawTerminal(cli.terminalFd, cli.out) if err != nil { return err } diff --git a/term/term.go b/term/term.go index f4d66a71d6..074319c287 100644 --- a/term/term.go +++ b/term/term.go @@ -1,6 +1,8 @@ package term import ( + "fmt" + "io" "os" "os/signal" "syscall" @@ -43,17 +45,43 @@ func RestoreTerminal(fd uintptr, state *State) error { return err } -func SetRawTerminal(fd uintptr) (*State, error) { +func SaveState(fd uintptr) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 { + return nil, err + } + + return &oldState, nil +} + +func DisableEcho(fd uintptr, out io.Writer, state *State) error { + newState := state.termios + newState.Lflag &^= syscall.ECHO + + HandleInterrupt(fd, out, state) + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 { + return err + } + return nil +} + +func HandleInterrupt(fd uintptr, out io.Writer, state *State) { + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, os.Interrupt) + + go func() { + _ = <-sigchan + fmt.Fprintf(out, "\n") + RestoreTerminal(fd, state) + os.Exit(0) + }() +} + +func SetRawTerminal(fd uintptr, out io.Writer) (*State, error) { oldState, err := MakeRaw(fd) if err != nil { return nil, err } - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - go func() { - _ = <-c - RestoreTerminal(fd, oldState) - os.Exit(0) - }() + HandleInterrupt(fd, out, oldState) return oldState, err } From 6e4a818ee65008a043d3ba8e5053cc43babb2f66 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 17:27:15 -0700 Subject: [PATCH 16/23] Exit if there is any error reading from stdin. --- commands.go | 13 +++++++------ term/term.go | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/commands.go b/commands.go index 0643bc8d58..ecd9c63155 100644 --- a/commands.go +++ b/commands.go @@ -271,13 +271,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } } - readInput := func(in io.Reader) (string, error) { + readInput := func(in io.Reader, out io.Writer) string { reader := bufio.NewReader(in) line, err := reader.ReadString('\n') if err != nil { - return "", err + fmt.Fprintln(out, err.Error()) + os.Exit(1) } - return line, nil + return line } authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] @@ -287,7 +288,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if username == "" { promptDefault("Username", authconfig.Username) - username, _ = readInput(cli.in) + username = readInput(cli.in, cli.out) if username == "" { username = authconfig.Username } @@ -299,7 +300,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { fmt.Fprintf(cli.out, "Password: ") term.DisableEcho(cli.terminalFd, cli.out, oldState) - password, _ = readInput(cli.in) + password = readInput(cli.in, cli.out) term.RestoreTerminal(cli.terminalFd, oldState) @@ -310,7 +311,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if email == "" { promptDefault("\nEmail", authconfig.Email) - email, _ = readInput(cli.in) + email = readInput(cli.in, cli.out) if email == "" { email = authconfig.Email } diff --git a/term/term.go b/term/term.go index 074319c287..2c78d6806e 100644 --- a/term/term.go +++ b/term/term.go @@ -71,7 +71,7 @@ func HandleInterrupt(fd uintptr, out io.Writer, state *State) { go func() { _ = <-sigchan - fmt.Fprintf(out, "\n") + fmt.Fprint(out, "\n") RestoreTerminal(fd, state) os.Exit(0) }() From f18889bf674e574a3ac1315ed7bbd56bf638e6c5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 21:30:07 -0700 Subject: [PATCH 17/23] Print a new line after getting the password from stdin. --- commands.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index ecd9c63155..b58a2e360d 100644 --- a/commands.go +++ b/commands.go @@ -301,6 +301,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { term.DisableEcho(cli.terminalFd, cli.out, oldState) password = readInput(cli.in, cli.out) + fmt.Fprint(cli.out, "\n") term.RestoreTerminal(cli.terminalFd, oldState) @@ -310,7 +311,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } if email == "" { - promptDefault("\nEmail", authconfig.Email) + promptDefault("Email", authconfig.Email) email = readInput(cli.in, cli.out) if email == "" { email = authconfig.Email From b54ba5095bc8b9323a3649806df98345cbe1f3b0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 21:08:00 -0700 Subject: [PATCH 18/23] Add the ISIG syscall back to not kill the client withing a shell with ctrl+c. --- term/termios_darwin.go | 2 +- term/termios_linux.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/term/termios_darwin.go b/term/termios_darwin.go index 0f6b24b184..24e79de4b2 100644 --- a/term/termios_darwin.go +++ b/term/termios_darwin.go @@ -44,7 +44,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (ISTRIP | INLCR | IGNCR | IXON | IXOFF) newState.Iflag |= ICRNL newState.Oflag |= ONLCR - newState.Lflag &^= (ECHO | ICANON) + newState.Lflag &^= (ECHO | ICANON | ISIG) if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { return nil, err diff --git a/term/termios_linux.go b/term/termios_linux.go index 22f4fff430..6a76460a54 100644 --- a/term/termios_linux.go +++ b/term/termios_linux.go @@ -33,7 +33,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.IEXTEN) + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.EXTEN) newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) newState.Cflag |= syscall.CS8 From b8a89628339dd63b5f2ff3d28715a5431412b65a Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 21:29:37 -0700 Subject: [PATCH 19/23] Simplify term signal handler. --- commands.go | 5 +++-- term/term.go | 25 ++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/commands.go b/commands.go index b58a2e360d..3dbcc41cf6 100644 --- a/commands.go +++ b/commands.go @@ -299,7 +299,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error { oldState, _ := term.SaveState(cli.terminalFd) fmt.Fprintf(cli.out, "Password: ") - term.DisableEcho(cli.terminalFd, cli.out, oldState) + term.DisableEcho(cli.terminalFd, oldState) + password = readInput(cli.in, cli.out) fmt.Fprint(cli.out, "\n") @@ -1647,7 +1648,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea } if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" { - oldState, err := term.SetRawTerminal(cli.terminalFd, cli.out) + oldState, err := term.SetRawTerminal(cli.terminalFd) if err != nil { return err } diff --git a/term/term.go b/term/term.go index 2c78d6806e..d8d4d1a655 100644 --- a/term/term.go +++ b/term/term.go @@ -54,34 +54,33 @@ func SaveState(fd uintptr) (*State, error) { return &oldState, nil } -func DisableEcho(fd uintptr, out io.Writer, state *State) error { +func DisableEcho(fd uintptr, state *State) error { newState := state.termios newState.Lflag &^= syscall.ECHO - HandleInterrupt(fd, out, state) if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 { return err } + handleInterrupt(fd, state) return nil } -func HandleInterrupt(fd uintptr, out io.Writer, state *State) { +func SetRawTerminal(fd uintptr) (*State, error) { + oldState, err := MakeRaw(fd) + if err != nil { + return nil, err + } + handleInterrupt(fd, oldState) + return oldState, err +} + +func handleInterrupt(fd uintptr, state *State) { sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, os.Interrupt) go func() { _ = <-sigchan - fmt.Fprint(out, "\n") RestoreTerminal(fd, state) os.Exit(0) }() } - -func SetRawTerminal(fd uintptr, out io.Writer) (*State, error) { - oldState, err := MakeRaw(fd) - if err != nil { - return nil, err - } - HandleInterrupt(fd, out, oldState) - return oldState, err -} From e7ee2f443ad93803e8312bc224da618d555aebdc Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 22:22:11 -0700 Subject: [PATCH 20/23] Remove unused imports. --- term/term.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/term/term.go b/term/term.go index d8d4d1a655..5929c2caa1 100644 --- a/term/term.go +++ b/term/term.go @@ -1,8 +1,6 @@ package term import ( - "fmt" - "io" "os" "os/signal" "syscall" From 78d995bbd6dc8022faef251835ae6001c4c3da49 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 22:22:24 -0700 Subject: [PATCH 21/23] Fix syscall name. --- term/termios_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/term/termios_linux.go b/term/termios_linux.go index 6a76460a54..4a717c84a7 100644 --- a/term/termios_linux.go +++ b/term/termios_linux.go @@ -33,7 +33,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.EXTEN) + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN) newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) newState.Cflag |= syscall.CS8 From 9f8e5a93b4b7c6b190177c73085e7d2d2d93c64b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 22:28:05 -0700 Subject: [PATCH 22/23] Use flag.StringVar to capture the command line flags. --- commands.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index 3dbcc41cf6..be2a4c02d4 100644 --- a/commands.go +++ b/commands.go @@ -254,9 +254,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error { func (cli *DockerCli) CmdLogin(args ...string) error { cmd := Subcmd("login", "[OPTIONS]", "Register or Login to the docker registry server") - username := *cmd.String("u", "", "username") - password := *cmd.String("p", "", "password") - email := *cmd.String("e", "", "email") + var username, password, email string + + cmd.StringVar(&username, "u", "", "username") + cmd.StringVar(&password, "p", "", "password") + cmd.StringVar(&email, "e", "", "email") err := cmd.Parse(args) if err != nil { From ce53e21ea6790cf7c2e96f8c5f0725bdf41a80f0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 1 Sep 2013 16:12:07 -0700 Subject: [PATCH 23/23] Read the stdin line properly. Load the auth config before it's used. --- commands.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 95f67f4f33..7760108588 100644 --- a/commands.go +++ b/commands.go @@ -277,14 +277,15 @@ func (cli *DockerCli) CmdLogin(args ...string) error { readInput := func(in io.Reader, out io.Writer) string { reader := bufio.NewReader(in) - line, err := reader.ReadString('\n') + line, _, err := reader.ReadLine() if err != nil { fmt.Fprintln(out, err.Error()) os.Exit(1) } - return line + return string(line) } + cli.LoadConfigFile() authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] if !ok { authconfig = auth.AuthConfig{}