3f5eb79005deeecbd68a0963a7f0db0ff6747333
[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 #define CONTROL_D ('D' - 'A' + 1)
43
44
45 #ifdef __MINGW32__ /* use the odd Win32 functions */
46 static struct {
47     HANDLE in, out;
48 } con;
49 #define DEF_INPMODE  (ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT    \
50                                         |ENABLE_PROCESSED_INPUT )
51 #define HID_INPMODE  (ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT )
52 #define DEF_OUTMODE  (ENABLE_WRAP_AT_EOL_OUTPUT|ENABLE_PROCESSED_OUTPUT)
53
54 #else /* yeah, we have a real OS */
55 static FILE *ttyfp = NULL;
56 #endif
57
58 static int initialized;
59 static int last_prompt_len;
60 static int batchmode;
61 static int no_terminal;
62
63 #ifdef HAVE_TCGETATTR
64 static struct termios termsave;
65 static int restore_termios;
66 #endif
67
68 #ifdef HAVE_TCGETATTR
69 static void
70 cleanup(void)
71 {
72     if( restore_termios ) {
73         restore_termios = 0; /* do it prios in case it is interrupted again */
74         if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) )
75             log_error("tcsetattr() failed: %s\n", strerror(errno) );
76     }
77 }
78 #endif
79
80 static void
81 init_ttyfp(void)
82 {
83     if( initialized )
84         return;
85
86   #if defined(__MINGW32__)
87     {
88         SECURITY_ATTRIBUTES sa;
89
90         memset(&sa, 0, sizeof(sa));
91         sa.nLength = sizeof(sa);
92         sa.bInheritHandle = TRUE;
93         con.out = CreateFileA( "CONOUT$", GENERIC_READ|GENERIC_WRITE,
94                                FILE_SHARE_READ|FILE_SHARE_WRITE,
95                                &sa, OPEN_EXISTING, 0, 0 );
96         if( con.out == INVALID_HANDLE_VALUE )
97             log_fatal("open(CONOUT$) failed: rc=%d", (int)GetLastError() );
98         memset(&sa, 0, sizeof(sa));
99         sa.nLength = sizeof(sa);
100         sa.bInheritHandle = TRUE;
101         con.in = CreateFileA( "CONIN$", GENERIC_READ|GENERIC_WRITE,
102                                FILE_SHARE_READ|FILE_SHARE_WRITE,
103                                &sa, OPEN_EXISTING, 0, 0 );
104         if( con.in == INVALID_HANDLE_VALUE )
105             log_fatal("open(CONIN$) failed: rc=%d", (int)GetLastError() );
106     }
107     SetConsoleMode(con.in, DEF_INPMODE );
108     SetConsoleMode(con.out, DEF_OUTMODE );
109
110   #elif defined(__EMX__)
111     ttyfp = stdout; /* Fixme: replace by the real functions: see wklib */
112   #else
113     ttyfp = batchmode? stderr : fopen("/dev/tty", "r+");
114     if( !ttyfp ) {
115         log_error("cannot open /dev/tty: %s\n", strerror(errno) );
116         exit(2);
117     }
118   #endif
119   #ifdef HAVE_TCGETATTR
120     atexit( cleanup );
121   #endif
122     initialized = 1;
123 }
124
125 int
126 tty_batchmode( int onoff )
127 {
128     int old = batchmode;
129     if( onoff != -1 )
130         batchmode = onoff;
131     return old;
132 }
133
134 int
135 tty_no_terminal(int onoff)
136 {
137     int old = no_terminal;
138     no_terminal = onoff ? 1 : 0;
139     return old;
140 }
141
142 void
143 tty_printf( const char *fmt, ... )
144 {
145     va_list arg_ptr;
146
147     if (no_terminal)
148         return;
149
150     if( !initialized )
151         init_ttyfp();
152
153     va_start( arg_ptr, fmt ) ;
154   #ifdef __MINGW32__
155     { static char *buf;
156       static size_t bufsize;
157         int n;
158         DWORD nwritten;
159
160       #if 0 /* the dox say, that there is a snprintf, but I didn't found
161              * it, so we use a static buffer for now */
162         do {
163             if( n == -1 || !buf ) {
164                 m_free(buf);
165                 bufsize += 200;
166                 /* better check the new size; (we use M$ functions) */
167                 if( bufsize > 50000 )
168                     log_bug("vsnprintf probably failed\n");
169                 buf = m_alloc( bufsize );
170             }
171             n = _vsnprintf(buf, bufsize-1, fmt, arg_ptr);
172         } while( n == -1 );
173       #else
174         if( !buf ) {
175             bufsize += 1000;
176             buf = m_alloc( bufsize );
177         }
178         n = vsprintf(buf, fmt, arg_ptr);
179         if( n == -1 )
180             log_bug("vsprintf() failed\n");
181       #endif
182
183         if( !WriteConsoleA( con.out, buf, n, &nwritten, NULL ) )
184             log_fatal("WriteConsole failed: rc=%d", (int)GetLastError() );
185         if( n != nwritten )
186             log_fatal("WriteConsole failed: %d != %ld\n", n, nwritten );
187         last_prompt_len += n;
188     }
189   #else
190     last_prompt_len += vfprintf(ttyfp,fmt,arg_ptr) ;
191     fflush(ttyfp);
192   #endif
193     va_end(arg_ptr);
194 }
195
196
197 /****************
198  * Print a string, but filter all control characters out.
199  */
200 void
201 tty_print_string( byte *p, size_t n )
202 {
203     if (no_terminal)
204         return;
205
206     if( !initialized )
207         init_ttyfp();
208
209   #ifdef __MINGW32__
210     /* not so effective, change it if you want */
211     for( ; n; n--, p++ )
212         if( iscntrl( *p ) ) {
213             if( *p == '\n' )
214                 tty_printf("\\n");
215             else if( !*p )
216                 tty_printf("\\0");
217             else
218                 tty_printf("\\x%02x", *p);
219         }
220         else
221             tty_printf("%c", *p);
222   #else
223     for( ; n; n--, p++ )
224         if( iscntrl( *p ) ) {
225             putc('\\', ttyfp);
226             if( *p == '\n' )
227                 putc('n', ttyfp);
228             else if( !*p )
229                 putc('0', ttyfp);
230             else
231                 fprintf(ttyfp, "x%02x", *p );
232         }
233         else
234             putc(*p, ttyfp);
235   #endif
236 }
237
238
239
240
241
242 static char *
243 do_get( const char *prompt, int hidden )
244 {
245     char *buf;
246     byte cbuf[1];
247     int c, n, i;
248
249     if( batchmode ) {
250         log_error("Sorry, we are in batchmode - can't get input\n");
251         exit(2);
252     }
253
254     if (no_terminal) {
255         log_error("Sorry, no terminal at all requested - can't get input\n");
256         exit(2);
257     }
258
259     if( !initialized )
260         init_ttyfp();
261
262     last_prompt_len = 0;
263     tty_printf( prompt );
264     buf = m_alloc(n=50);
265     i = 0;
266
267   #if __MINGW32__ /* windoze version */
268     if( hidden )
269         SetConsoleMode(con.in, HID_INPMODE );
270
271     for(;;) {
272         DWORD nread;
273
274         if( !ReadConsoleA( con.in, cbuf, 1, &nread, NULL ) )
275             log_fatal("ReadConsole failed: rc=%d", (int)GetLastError() );
276         if( !nread )
277             continue;
278         if( *cbuf == '\n' )
279             break;
280
281         if( !hidden )
282             last_prompt_len++;
283         c = *cbuf;
284         if( c == '\t' )
285             c = ' ';
286         else if( c > 0xa0 )
287             ; /* we don't allow 0xa0, as this is a protected blank which may
288                * confuse the user */
289         else if( iscntrl(c) )
290             continue;
291         if( !(i < n-1) ) {
292             n += 50;
293             buf = m_realloc( buf, n );
294         }
295         buf[i++] = c;
296     }
297
298     if( hidden )
299         SetConsoleMode(con.in, DEF_INPMODE );
300
301   #else /* unix version */
302     if( hidden ) {
303       #ifdef HAVE_TCGETATTR
304         struct termios term;
305
306         if( tcgetattr(fileno(ttyfp), &termsave) )
307             log_fatal("tcgetattr() failed: %s\n", strerror(errno) );
308         restore_termios = 1;
309         term = termsave;
310         term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
311         if( tcsetattr( fileno(ttyfp), TCSAFLUSH, &term ) )
312             log_fatal("tcsetattr() failed: %s\n", strerror(errno) );
313       #endif
314     }
315
316     /* fixme: How can we avoid that the \n is echoed w/o disabling
317      * canonical mode - w/o this kill_prompt can't work */
318     while( read(fileno(ttyfp), cbuf, 1) == 1 && *cbuf != '\n' ) {
319         if( !hidden )
320             last_prompt_len++;
321         c = *cbuf;
322         if( c == CONTROL_D )
323             log_info("control d found\n");
324         if( c == '\t' )
325             c = ' ';
326         else if( c > 0xa0 )
327             ; /* we don't allow 0xa0, as this is a protected blank which may
328                * confuse the user */
329         else if( iscntrl(c) )
330             continue;
331         if( !(i < n-1) ) {
332             n += 50;
333             buf = m_realloc( buf, n );
334         }
335         buf[i++] = c;
336     }
337     if( *cbuf != '\n' ) {
338         buf[0] = CONTROL_D;
339         i = 1;
340     }
341
342
343     if( hidden ) {
344       #ifdef HAVE_TCGETATTR
345         if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) )
346             log_error("tcsetattr() failed: %s\n", strerror(errno) );
347         restore_termios = 0;
348       #endif
349     }
350   #endif /* end unix version */
351     buf[i] = 0;
352     return buf;
353 }
354
355
356 char *
357 tty_get( const char *prompt )
358 {
359     return do_get( prompt, 0 );
360 }
361
362 char *
363 tty_get_hidden( const char *prompt )
364 {
365     return do_get( prompt, 1 );
366 }
367
368
369 void
370 tty_kill_prompt()
371 {
372     if ( no_terminal )
373         return;
374
375     if( !initialized )
376         init_ttyfp();
377
378     if( batchmode )
379         last_prompt_len = 0;
380     if( !last_prompt_len )
381         return;
382   #if __MINGW32__
383     tty_printf("\r%*s\r", last_prompt_len, "");
384   #else
385     {
386         int i;
387         putc('\r', ttyfp);
388         for(i=0; i < last_prompt_len; i ++ )
389             putc(' ', ttyfp);
390         putc('\r', ttyfp);
391         fflush(ttyfp);
392     }
393   #endif
394     last_prompt_len = 0;
395 }
396
397
398 int
399 tty_get_answer_is_yes( const char *prompt )
400 {
401     int yes;
402     char *p = tty_get( prompt );
403     tty_kill_prompt();
404     yes = answer_is_yes(p);
405     m_free(p);
406     return yes;
407 }
408