38143cba574b684b0470bc1209e9cd8dbb1160f1
[gnupg.git] / util / ttyio.c
1 /* ttyio.c -  tty i/O functions
2  *      Copyright (C) 1998 Free Software Foundation, Inc.
3  *
4  * This file is part of GNUPG.
5  *
6  * GNUPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * GNUPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <stdarg.h>
26 #include <unistd.h>
27 #ifdef HAVE_TCGETATTR
28   #include <termios.h>
29 #endif
30 #ifdef __MINGW32__ /* use the odd Win32 functions */
31   #include <windows.h>
32   #ifdef HAVE_TCGETATTR
33      #error mingw32 and termios
34   #endif
35 #endif
36 #include <errno.h>
37 #include <ctype.h>
38 #include "util.h"
39 #include "memory.h"
40 #include "ttyio.h"
41
42
43 #ifdef __MINGW32__ /* use the odd Win32 functions */
44 static struct {
45     HANDLE in, out;
46 } con;
47 #define DEF_INPMODE  (ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT    \
48                                         |ENABLE_PROCESSED_INPUT )
49 #define HID_INPMODE  (ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT )
50 #define DEF_OUTMODE  (ENABLE_WRAP_AT_EOL_OUTPUT|ENABLE_PROCESSED_OUTPUT)
51
52 #else /* yeah, we have a real OS */
53 static FILE *ttyfp = NULL;
54 #endif
55
56 static int initialized;
57 static int last_prompt_len;
58
59 #ifdef HAVE_TCGETATTR
60 static struct termios termsave;
61 static int restore_termios;
62 #endif
63
64 #ifdef HAVE_TCGETATTR
65 static void
66 cleanup(void)
67 {
68     if( restore_termios ) {
69         restore_termios = 0; /* do it prios in case it is interrupted again */
70         if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) )
71             log_error("tcsetattr() failed: %s\n", strerror(errno) );
72     }
73 }
74 #endif
75
76 static void
77 init_ttyfp()
78 {
79     if( initialized )
80         return;
81
82   #if defined(__MINGW32__)
83     {
84         SECURITY_ATTRIBUTES sa;
85
86         memset(&sa, 0, sizeof(sa));
87         sa.nLength = sizeof(sa);
88         sa.bInheritHandle = TRUE;
89         con.out = CreateFileA( "CONOUT$", GENERIC_READ|GENERIC_WRITE,
90                                FILE_SHARE_READ|FILE_SHARE_WRITE,
91                                &sa, OPEN_EXISTING, 0, 0 );
92         if( con.out == INVALID_HANDLE_VALUE )
93             log_fatal("open(CONOUT$) failed: rc=%d", (int)GetLastError() );
94         memset(&sa, 0, sizeof(sa));
95         sa.nLength = sizeof(sa);
96         sa.bInheritHandle = TRUE;
97         con.in = CreateFileA( "CONIN$", GENERIC_READ|GENERIC_WRITE,
98                                FILE_SHARE_READ|FILE_SHARE_WRITE,
99                                &sa, OPEN_EXISTING, 0, 0 );
100         if( con.in == INVALID_HANDLE_VALUE )
101             log_fatal("open(CONIN$) failed: rc=%d", (int)GetLastError() );
102     }
103     SetConsoleMode(con.in, DEF_INPMODE );
104     SetConsoleMode(con.out, DEF_OUTMODE );
105
106   #else
107     ttyfp = fopen("/dev/tty", "r+");
108     if( !ttyfp )
109         log_fatal("cannot open /dev/tty: %s\n", strerror(errno) );
110   #endif
111   #ifdef HAVE_TCGETATTR
112     atexit( cleanup );
113   #endif
114     initialized = 1;
115 }
116
117
118 void
119 tty_printf( const char *fmt, ... )
120 {
121     va_list arg_ptr;
122
123     if( !initialized )
124         init_ttyfp();
125
126     va_start( arg_ptr, fmt ) ;
127   #ifdef __MINGW32__
128     { static char *buf;
129       static size_t bufsize;
130         int n;
131         DWORD nwritten;
132
133       #if 0 /* the dox say, that there is a snprintf, but I didn't found
134              * it, so we use a static buffer for now */
135         do {
136             if( n == -1 || !buf ) {
137                 m_free(buf);
138                 bufsize += 200;
139                 /* better check the new size; (we use M$ functions) */
140                 if( bufsize > 50000 )
141                     log_bug("vsnprintf probably failed\n");
142                 buf = m_alloc( bufsize );
143             }
144             n = _vsnprintf(buf, bufsize-1, fmt, arg_ptr);
145         } while( n == -1 );
146       #else
147         if( !buf ) {
148             bufsize += 1000;
149             buf = m_alloc( bufsize );
150         }
151         n = vsprintf(buf, fmt, arg_ptr);
152         if( n == -1 )
153             log_bug("vsprintf() failed\n");
154       #endif
155
156         if( !WriteConsoleA( con.out, buf, n, &nwritten, NULL ) )
157             log_fatal("WriteConsole failed: rc=%d", (int)GetLastError() );
158         if( n != nwritten )
159             log_fatal("WriteConsole failed: %d != %ld\n", n, nwritten );
160         last_prompt_len += n;
161     }
162   #else
163     last_prompt_len += vfprintf(ttyfp,fmt,arg_ptr) ;
164     fflush(ttyfp);
165   #endif
166     va_end(arg_ptr);
167 }
168
169
170 /****************
171  * Print a string, but filter all control characters out.
172  */
173 void
174 tty_print_string( byte *p, size_t n )
175 {
176     if( !initialized )
177         init_ttyfp();
178
179   #ifdef __MINGW32__
180     /* not so effective, change it if you want */
181     for( ; n; n--, p++ )
182         if( iscntrl( *p ) ) {
183             if( *p == '\n' )
184                 tty_printf("\\n");
185             else if( !*p )
186                 tty_printf("\\0");
187             else
188                 tty_printf("\\x%02x", *p);
189         }
190         else
191             tty_printf("%c", *p);
192   #else
193     for( ; n; n--, p++ )
194         if( iscntrl( *p ) ) {
195             putc('\\', ttyfp);
196             if( *p == '\n' )
197                 putc('n', ttyfp);
198             else if( !*p )
199                 putc('0', ttyfp);
200             else
201                 fprintf(ttyfp, "x%02x", *p );
202         }
203         else
204             putc(*p, ttyfp);
205   #endif
206 }
207
208
209
210
211
212 static char *
213 do_get( const char *prompt, int hidden )
214 {
215     char *buf;
216     byte cbuf[1];
217     int c, n, i;
218
219     if( !initialized )
220         init_ttyfp();
221
222     last_prompt_len = 0;
223     tty_printf( prompt );
224     buf = m_alloc(n=50);
225     i = 0;
226
227   #if __MINGW32__ /* windoze version */
228     if( hidden )
229         SetConsoleMode(con.in, HID_INPMODE );
230
231     for(;;) {
232         DWORD nread;
233
234         if( !ReadConsoleA( con.in, cbuf, 1, &nread, NULL ) )
235             log_fatal("ReadConsole failed: rc=%d", (int)GetLastError() );
236         if( !nread )
237             continue;
238         if( *cbuf == '\n' )
239             break;
240
241         if( !hidden )
242             last_prompt_len++;
243         c = *cbuf;
244         if( c == '\t' )
245             c = ' ';
246         else if( c > 0xa0 )
247             ; /* we don't allow 0xa0, as this is a protected blank which may
248                * confuse the user */
249         else if( iscntrl(c) )
250             continue;
251         if( !(i < n-1) ) {
252             n += 50;
253             buf = m_realloc( buf, n );
254         }
255         buf[i++] = c;
256     }
257
258     if( hidden )
259         SetConsoleMode(con.in, DEF_INPMODE );
260
261   #else /* unix version */
262     if( hidden ) {
263       #ifdef HAVE_TCGETATTR
264         struct termios term;
265
266         if( tcgetattr(fileno(ttyfp), &termsave) )
267             log_fatal("tcgetattr() failed: %s\n", strerror(errno) );
268         restore_termios = 1;
269         term = termsave;
270         term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
271         if( tcsetattr( fileno(ttyfp), TCSAFLUSH, &term ) )
272             log_fatal("tcsetattr() failed: %s\n", strerror(errno) );
273       #endif
274     }
275
276     /* fixme: How can we avoid that the \n is echoed w/o disabling
277      * canonical mode - w/o this kill_prompt can't work */
278     while( read(fileno(ttyfp), cbuf, 1) == 1 && *cbuf != '\n' ) {
279         if( !hidden )
280             last_prompt_len++;
281         c = *cbuf;
282         if( c == '\t' )
283             c = ' ';
284         else if( c > 0xa0 )
285             ; /* we don't allow 0xa0, as this is a protected blank which may
286                * confuse the user */
287         else if( iscntrl(c) )
288             continue;
289         if( !(i < n-1) ) {
290             n += 50;
291             buf = m_realloc( buf, n );
292         }
293         buf[i++] = c;
294     }
295
296
297     if( hidden ) {
298       #ifdef HAVE_TCGETATTR
299         if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) )
300             log_error("tcsetattr() failed: %s\n", strerror(errno) );
301         restore_termios = 0;
302       #endif
303     }
304   #endif /* end unix version */
305     buf[i] = 0;
306     return buf;
307 }
308
309
310 char *
311 tty_get( const char *prompt )
312 {
313     return do_get( prompt, 0 );
314 }
315
316 char *
317 tty_get_hidden( const char *prompt )
318 {
319     return do_get( prompt, 1 );
320 }
321
322
323 void
324 tty_kill_prompt()
325 {
326
327     if( !initialized )
328         init_ttyfp();
329     if( !last_prompt_len )
330         return;
331   #if __MINGW32__
332     tty_printf("\r%*s\r", last_prompt_len, "");
333   #else
334     {
335         int i;
336         putc('\r', ttyfp);
337         for(i=0; i < last_prompt_len; i ++ )
338             putc(' ', ttyfp);
339         putc('\r', ttyfp);
340         fflush(ttyfp);
341     }
342   #endif
343     last_prompt_len = 0;
344 }
345
346
347 int
348 tty_get_answer_is_yes( const char *prompt )
349 {
350     int yes;
351     char *p = tty_get( prompt );
352     tty_kill_prompt();
353     yes = answer_is_yes(p);
354     m_free(p);
355     return yes;
356 }
357