Skip to content

Commit

Permalink
Merge pull request twpayne#1322 from twpayne/fix-1311
Browse files Browse the repository at this point in the history
Use terminal to read password on Windows
  • Loading branch information
twpayne committed Jul 31, 2021
2 parents 7dd3eb2 + aae3a82 commit 482ad86
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 142 deletions.
6 changes: 3 additions & 3 deletions internal/chezmoi/chezmoi_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import (
"io/fs"
"regexp"
"strings"
"syscall"

vfs "github.com/twpayne/go-vfs/v3"
"golang.org/x/sys/unix"
)

var whitespaceRx = regexp.MustCompile(`\s+`)

func init() {
Umask = fs.FileMode(syscall.Umask(0))
syscall.Umask(int(Umask))
Umask = fs.FileMode(unix.Umask(0))
unix.Umask(int(Umask))
}

// FQDNHostname returns the FQDN hostname, if it can be determined.
Expand Down
4 changes: 2 additions & 2 deletions internal/chezmoitest/chezmoitest_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
package chezmoitest

import (
"syscall"
"golang.org/x/sys/unix"
)

var (
Expand All @@ -25,5 +25,5 @@ var (
)

func init() {
syscall.Umask(int(Umask))
unix.Umask(int(Umask))
}
79 changes: 7 additions & 72 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"bufio"
"bytes"
"encoding/json"
"errors"
Expand Down Expand Up @@ -1033,6 +1034,7 @@ func (c *Config) newRootCmd() (*cobra.Command, error) {
c.newGitCmd(),
c.newImportCmd(),
c.newInitCmd(),
c.newInternalTestCmd(),
c.newManagedCmd(),
c.newMergeCmd(),
c.newPurgeCmd(),
Expand Down Expand Up @@ -1342,27 +1344,15 @@ func (c *Config) readConfig() error {
}

func (c *Config) readLine(prompt string) (string, error) {
var line string
if err := c.withTerminal(prompt, func(t terminal) error {
var err error
line, err = t.ReadLine()
return err
}); err != nil {
_, err := c.stdout.Write([]byte(prompt))
if err != nil {
return "", err
}
return line, nil
}

func (c *Config) readPassword(prompt string) (string, error) {
var password string
if err := c.withTerminal("", func(t terminal) error {
var err error
password, err = t.ReadPassword(prompt)
return err
}); err != nil {
line, err := bufio.NewReader(c.stdin).ReadString('\n')
if err != nil {
return "", err
}
return password, nil
return strings.TrimSuffix(line, "\n"), nil
}

func (c *Config) run(dir chezmoi.AbsPath, name string, args []string) error {
Expand Down Expand Up @@ -1517,61 +1507,6 @@ func (c *Config) validateData() error {
return validateKeys(c.Data, identifierRx)
}

func (c *Config) withTerminal(prompt string, f func(terminal) error) error {
if c.noTTY || runtime.GOOS == "windows" {
return f(newDumbTerminal(c.stdin, c.stdout, prompt))
}

if stdinFile, ok := c.stdin.(*os.File); ok && term.IsTerminal(int(stdinFile.Fd())) {
fd := int(stdinFile.Fd())
width, height, err := term.GetSize(fd)
if err != nil {
return err
}
oldState, err := term.MakeRaw(fd)
if err != nil {
return err
}
defer func() {
_ = term.Restore(fd, oldState)
}()
t := term.NewTerminal(struct {
io.Reader
io.Writer
}{
Reader: c.stdin,
Writer: c.stdout,
}, prompt)
if err := t.SetSize(width, height); err != nil {
return err
}
return f(t)
}

devTTY, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return err
}
defer devTTY.Close()
fd := int(devTTY.Fd())
width, height, err := term.GetSize(fd)
if err != nil {
return err
}
oldState, err := term.MakeRaw(fd)
if err != nil {
return err
}
defer func() {
_ = term.Restore(fd, oldState)
}()
t := term.NewTerminal(devTTY, prompt)
if err := t.SetSize(width, height); err != nil {
return err
}
return f(t)
}

func (c *Config) writeOutput(data []byte) error {
if c.outputAbsPath == "" || c.outputAbsPath == "-" {
_, err := c.stdout.Write(data)
Expand Down
35 changes: 35 additions & 0 deletions internal/cmd/config_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//+build !windows

package cmd

import (
"errors"
"os"

"golang.org/x/term"
)

func (c *Config) readPassword(prompt string) (string, error) {
if c.noTTY {
return c.readLine(prompt)
}

tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return "", err
}
defer func() {
_ = tty.Close()
}()
if _, err := tty.Write([]byte(prompt)); err != nil {
return "", err
}
password, err := term.ReadPassword(int(tty.Fd()))
if err != nil && !errors.Is(err, term.ErrPasteIndicator) {
return "", err
}
if _, err := tty.Write([]byte{'\n'}); err != nil {
return "", err
}
return string(password), nil
}
35 changes: 35 additions & 0 deletions internal/cmd/config_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cmd

import (
"fmt"

"golang.org/x/sys/windows"
"golang.org/x/term"
)

func (c *Config) readPassword(prompt string) (string, error) {
if c.noTTY {
return c.readLine(prompt)
}

name, err := windows.UTF16PtrFromString("CONIN$")
if err != nil {
return "", err
}
handle, err := windows.CreateFile(name, windows.GENERIC_READ|windows.GENERIC_WRITE, windows.FILE_SHARE_READ, nil, windows.OPEN_EXISTING, 0, 0)
if err != nil {
return "", err
}
defer func() {
_ = windows.CloseHandle(handle)
}()
//nolint:forbidigo
fmt.Print(prompt)
password, err := term.ReadPassword(int(handle))
if err != nil {
return "", err
}
//nolint:forbidigo
fmt.Println("")
return string(password), nil
}
30 changes: 30 additions & 0 deletions internal/cmd/internaltestcmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cmd

import (
"github.com/spf13/cobra"
)

func (c *Config) newInternalTestCmd() *cobra.Command {
internalTestCmd := &cobra.Command{
Use: "internal-test",
Short: "Expose functionality for testing",
Hidden: true,
}

internalTestReadPasswordCmd := &cobra.Command{
Use: "read-password",
Short: "Read a password",
RunE: c.runInternalTestReadPasswordCmd,
}
internalTestCmd.AddCommand(internalTestReadPasswordCmd)

return internalTestCmd
}

func (c *Config) runInternalTestReadPasswordCmd(cmd *cobra.Command, args []string) error {
password, err := c.readPassword("Password? ")
if err != nil {
return err
}
return c.writeOutputString(password + "\n")
}
62 changes: 0 additions & 62 deletions internal/cmd/terminal.go

This file was deleted.

7 changes: 4 additions & 3 deletions internal/cmd/upgradecmd_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
vfs "github.com/twpayne/go-vfs/v3"
"golang.org/x/sys/unix"

"github.com/twpayne/chezmoi/v2/internal/chezmoi"
)
Expand Down Expand Up @@ -160,9 +161,9 @@ func (c *Config) runUpgradeCmd(cmd *cobra.Command, args []string) error {
Str("arg0", arg0).
Strs("argv", argv).
Msg("exec")
err = syscall.EINTR
for errors.Is(err, syscall.EINTR) {
err = syscall.Exec(arg0, argv, os.Environ())
err = unix.EINTR
for errors.Is(err, unix.EINTR) {
err = unix.Exec(arg0, argv, os.Environ())
}
return err
}
Expand Down

0 comments on commit 482ad86

Please sign in to comment.