瀏覽代碼

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.
David Calavera 12 年之前
父節點
當前提交
2357fecc92
共有 2 個文件被更改,包括 67 次插入88 次删除
  1. 32 81
      commands.go
  2. 35 7
      term/term.go

+ 32 - 81
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
 		}

+ 35 - 7
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) {
-	oldState, err := MakeRaw(fd)
-	if err != nil {
+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
 	}
-	c := make(chan os.Signal, 1)
-	signal.Notify(c, os.Interrupt)
+
+	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() {
-		_ = <-c
-		RestoreTerminal(fd, oldState)
+		_ = <-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
+	}
+	HandleInterrupt(fd, out, oldState)
 	return oldState, err
 }