Merge branch 'jk/reset-to-break-a-commit-doc'
[git] / compat / terminal.c
1 #include "git-compat-util.h"
2 #include "compat/terminal.h"
3 #include "sigchain.h"
4 #include "strbuf.h"
5
6 #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
7
8 static void restore_term(void);
9
10 static void restore_term_on_signal(int sig)
11 {
12         restore_term();
13         sigchain_pop(sig);
14         raise(sig);
15 }
16
17 #ifdef HAVE_DEV_TTY
18
19 #define INPUT_PATH "/dev/tty"
20 #define OUTPUT_PATH "/dev/tty"
21
22 static int term_fd = -1;
23 static struct termios old_term;
24
25 static void restore_term(void)
26 {
27         if (term_fd < 0)
28                 return;
29
30         tcsetattr(term_fd, TCSAFLUSH, &old_term);
31         close(term_fd);
32         term_fd = -1;
33 }
34
35 static int disable_echo(void)
36 {
37         struct termios t;
38
39         term_fd = open("/dev/tty", O_RDWR);
40         if (tcgetattr(term_fd, &t) < 0)
41                 goto error;
42
43         old_term = t;
44         sigchain_push_common(restore_term_on_signal);
45
46         t.c_lflag &= ~ECHO;
47         if (!tcsetattr(term_fd, TCSAFLUSH, &t))
48                 return 0;
49
50 error:
51         close(term_fd);
52         term_fd = -1;
53         return -1;
54 }
55
56 #elif defined(GIT_WINDOWS_NATIVE)
57
58 #define INPUT_PATH "CONIN$"
59 #define OUTPUT_PATH "CONOUT$"
60 #define FORCE_TEXT "t"
61
62 static HANDLE hconin = INVALID_HANDLE_VALUE;
63 static DWORD cmode;
64
65 static void restore_term(void)
66 {
67         if (hconin == INVALID_HANDLE_VALUE)
68                 return;
69
70         SetConsoleMode(hconin, cmode);
71         CloseHandle(hconin);
72         hconin = INVALID_HANDLE_VALUE;
73 }
74
75 static int disable_echo(void)
76 {
77         hconin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
78             FILE_SHARE_READ, NULL, OPEN_EXISTING,
79             FILE_ATTRIBUTE_NORMAL, NULL);
80         if (hconin == INVALID_HANDLE_VALUE)
81                 return -1;
82
83         GetConsoleMode(hconin, &cmode);
84         sigchain_push_common(restore_term_on_signal);
85         if (!SetConsoleMode(hconin, cmode & (~ENABLE_ECHO_INPUT))) {
86                 CloseHandle(hconin);
87                 hconin = INVALID_HANDLE_VALUE;
88                 return -1;
89         }
90
91         return 0;
92 }
93
94 #endif
95
96 #ifndef FORCE_TEXT
97 #define FORCE_TEXT
98 #endif
99
100 char *git_terminal_prompt(const char *prompt, int echo)
101 {
102         static struct strbuf buf = STRBUF_INIT;
103         int r;
104         FILE *input_fh, *output_fh;
105
106         input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT);
107         if (!input_fh)
108                 return NULL;
109
110         output_fh = fopen(OUTPUT_PATH, "w" FORCE_TEXT);
111         if (!output_fh) {
112                 fclose(input_fh);
113                 return NULL;
114         }
115
116         if (!echo && disable_echo()) {
117                 fclose(input_fh);
118                 fclose(output_fh);
119                 return NULL;
120         }
121
122         fputs(prompt, output_fh);
123         fflush(output_fh);
124
125         r = strbuf_getline_lf(&buf, input_fh);
126         if (!echo) {
127                 putc('\n', output_fh);
128                 fflush(output_fh);
129         }
130
131         restore_term();
132         fclose(input_fh);
133         fclose(output_fh);
134
135         if (r == EOF)
136                 return NULL;
137         return buf.buf;
138 }
139
140 #else
141
142 char *git_terminal_prompt(const char *prompt, int echo)
143 {
144         return getpass(prompt);
145 }
146
147 #endif