Skip to content

Commit 21aeafc

Browse files
peffgitster
authored andcommitted
add generic terminal prompt function
When we need to prompt the user for input interactively, we want to access their terminal directly. We can't rely on stdio because it may be connected to pipes or files, rather than the terminal. Instead, we use "getpass()", because it abstracts the idea of prompting and reading from the terminal. However, it has some problems: 1. It never echoes the typed characters, which makes it OK for passwords but annoying for other input (like usernames). 2. Some implementations of getpass() have an extremely small input buffer (e.g., Solaris 8 is reported to support only 8 characters). 3. Some implementations of getpass() will fall back to reading from stdin (e.g., glibc). We explicitly don't want this, because our stdin may be connected to a pipe speaking a particular protocol, and reading will disrupt the protocol flow (e.g., the remote-curl helper). 4. Some implementations of getpass() turn off signals, so that hitting "^C" on the terminal does not break out of the password prompt. This can be a mild annoyance. Instead, let's provide an abstract "git_terminal_prompt" function that addresses these concerns. This patch includes an implementation based on /dev/tty, enabled by setting HAVE_DEV_TTY. The fallback is to use getpass() as before. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1cb0134 commit 21aeafc

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

Makefile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ all::
229229
#
230230
# Define NO_REGEX if you have no or inferior regex support in your C library.
231231
#
232+
# Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the
233+
# user.
234+
#
232235
# Define GETTEXT_POISON if you are debugging the choice of strings marked
233236
# for translation. In a GETTEXT_POISON build, you can turn all strings marked
234237
# for translation into gibberish by setting the GIT_GETTEXT_POISON variable
@@ -523,6 +526,7 @@ LIB_H += compat/bswap.h
523526
LIB_H += compat/cygwin.h
524527
LIB_H += compat/mingw.h
525528
LIB_H += compat/obstack.h
529+
LIB_H += compat/terminal.h
526530
LIB_H += compat/win32/pthread.h
527531
LIB_H += compat/win32/syslog.h
528532
LIB_H += compat/win32/poll.h
@@ -610,6 +614,7 @@ LIB_OBJS += color.o
610614
LIB_OBJS += combine-diff.o
611615
LIB_OBJS += commit.o
612616
LIB_OBJS += compat/obstack.o
617+
LIB_OBJS += compat/terminal.o
613618
LIB_OBJS += config.o
614619
LIB_OBJS += connect.o
615620
LIB_OBJS += connected.o
@@ -1646,6 +1651,10 @@ ifdef HAVE_PATHS_H
16461651
BASIC_CFLAGS += -DHAVE_PATHS_H
16471652
endif
16481653

1654+
ifdef HAVE_DEV_TTY
1655+
BASIC_CFLAGS += -DHAVE_DEV_TTY
1656+
endif
1657+
16491658
ifdef DIR_HAS_BSD_GROUP_SEMANTICS
16501659
COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
16511660
endif

compat/terminal.c

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#include "git-compat-util.h"
2+
#include "compat/terminal.h"
3+
#include "sigchain.h"
4+
#include "strbuf.h"
5+
6+
#ifdef HAVE_DEV_TTY
7+
8+
static int term_fd = -1;
9+
static struct termios old_term;
10+
11+
static void restore_term(void)
12+
{
13+
if (term_fd < 0)
14+
return;
15+
16+
tcsetattr(term_fd, TCSAFLUSH, &old_term);
17+
term_fd = -1;
18+
}
19+
20+
static void restore_term_on_signal(int sig)
21+
{
22+
restore_term();
23+
sigchain_pop(sig);
24+
raise(sig);
25+
}
26+
27+
char *git_terminal_prompt(const char *prompt, int echo)
28+
{
29+
static struct strbuf buf = STRBUF_INIT;
30+
int r;
31+
FILE *fh;
32+
33+
fh = fopen("/dev/tty", "w+");
34+
if (!fh)
35+
return NULL;
36+
37+
if (!echo) {
38+
struct termios t;
39+
40+
if (tcgetattr(fileno(fh), &t) < 0) {
41+
fclose(fh);
42+
return NULL;
43+
}
44+
45+
old_term = t;
46+
term_fd = fileno(fh);
47+
sigchain_push_common(restore_term_on_signal);
48+
49+
t.c_lflag &= ~ECHO;
50+
if (tcsetattr(fileno(fh), TCSAFLUSH, &t) < 0) {
51+
term_fd = -1;
52+
fclose(fh);
53+
return NULL;
54+
}
55+
}
56+
57+
fputs(prompt, fh);
58+
fflush(fh);
59+
60+
r = strbuf_getline(&buf, fh, '\n');
61+
if (!echo) {
62+
putc('\n', fh);
63+
fflush(fh);
64+
}
65+
66+
restore_term();
67+
fclose(fh);
68+
69+
if (r == EOF)
70+
return NULL;
71+
return buf.buf;
72+
}
73+
74+
#else
75+
76+
char *git_terminal_prompt(const char *prompt, int echo)
77+
{
78+
return getpass(prompt);
79+
}
80+
81+
#endif

compat/terminal.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#ifndef COMPAT_TERMINAL_H
2+
#define COMPAT_TERMINAL_H
3+
4+
char *git_terminal_prompt(const char *prompt, int echo);
5+
6+
#endif /* COMPAT_TERMINAL_H */

0 commit comments

Comments
 (0)