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 }