1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris
6
7// Package terminal provides support functions for dealing with terminals, as
8// commonly found on UNIX systems.
9//
10// Putting a terminal into raw mode is the most common requirement:
11//
12// 	oldState, err := terminal.MakeRaw(0)
13// 	if err != nil {
14// 	        panic(err)
15// 	}
16// 	defer terminal.Restore(0, oldState)
17package readline
18
19import (
20	"io"
21	"syscall"
22)
23
24// State contains the state of a terminal.
25type State struct {
26	termios Termios
27}
28
29// IsTerminal returns true if the given file descriptor is a terminal.
30func IsTerminal(fd int) bool {
31	_, err := getTermios(fd)
32	return err == nil
33}
34
35// MakeRaw put the terminal connected to the given file descriptor into raw
36// mode and returns the previous state of the terminal so that it can be
37// restored.
38func MakeRaw(fd int) (*State, error) {
39	var oldState State
40
41	if termios, err := getTermios(fd); err != nil {
42		return nil, err
43	} else {
44		oldState.termios = *termios
45	}
46
47	newState := oldState.termios
48	// This attempts to replicate the behaviour documented for cfmakeraw in
49	// the termios(3) manpage.
50	newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
51	// newState.Oflag &^= syscall.OPOST
52	newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
53	newState.Cflag &^= syscall.CSIZE | syscall.PARENB
54	newState.Cflag |= syscall.CS8
55
56	newState.Cc[syscall.VMIN] = 1
57	newState.Cc[syscall.VTIME] = 0
58
59	return &oldState, setTermios(fd, &newState)
60}
61
62// GetState returns the current state of a terminal which may be useful to
63// restore the terminal after a signal.
64func GetState(fd int) (*State, error) {
65	termios, err := getTermios(fd)
66	if err != nil {
67		return nil, err
68	}
69
70	return &State{termios: *termios}, nil
71}
72
73// Restore restores the terminal connected to the given file descriptor to a
74// previous state.
75func restoreTerm(fd int, state *State) error {
76	return setTermios(fd, &state.termios)
77}
78
79// ReadPassword reads a line of input from a terminal without local echo.  This
80// is commonly used for inputting passwords and other sensitive data. The slice
81// returned does not include the \n.
82func ReadPassword(fd int) ([]byte, error) {
83	oldState, err := getTermios(fd)
84	if err != nil {
85		return nil, err
86	}
87
88	newState := oldState
89	newState.Lflag &^= syscall.ECHO
90	newState.Lflag |= syscall.ICANON | syscall.ISIG
91	newState.Iflag |= syscall.ICRNL
92	if err := setTermios(fd, newState); err != nil {
93		return nil, err
94	}
95
96	defer func() {
97		setTermios(fd, oldState)
98	}()
99
100	var buf [16]byte
101	var ret []byte
102	for {
103		n, err := syscall.Read(fd, buf[:])
104		if err != nil {
105			return nil, err
106		}
107		if n == 0 {
108			if len(ret) == 0 {
109				return nil, io.EOF
110			}
111			break
112		}
113		if buf[n-1] == '\n' {
114			n--
115		}
116		ret = append(ret, buf[:n]...)
117		if n < len(buf) {
118			break
119		}
120	}
121
122	return ret, nil
123}
124