See ChangeLog: Tue Aug 31 17:20:44 CEST 1999 Werner Koch
[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 void
239 tty_print_utf8_string( byte *p, size_t n )
240 {
241     size_t i;
242     char *buf;
243
244     if (no_terminal)
245         return;
246
247     /* we can handle plain ascii simpler, so check for it first */
248     for(i=0; i < n; i++ ) {
249         if( p[i] & 0x80 )
250             break;
251     }
252     if( i < n ) {
253         buf = utf8_to_native( p, n );
254         tty_printf("%s", buf );
255         m_free( buf );
256     }
257     else
258         tty_print_string( p, n );
259 }
260
261
262
263
264
265 static char *
266 do_get( const char *prompt, int hidden )
267 {
268     char *buf;
269     byte cbuf[1];
270     int c, n, i;
271
272     if( batchmode ) {
273         log_error("Sorry, we are in batchmode - can't get input\n");
274         exit(2);
275     }
276
277     if (no_terminal) {
278         log_error("Sorry, no terminal at all requested - can't get input\n");
279         exit(2);
280     }
281
282     if( !initialized )
283         init_ttyfp();
284
285     last_prompt_len = 0;
286     tty_printf( prompt );
287     buf = m_alloc(n=50);
288     i = 0;
289
290   #if __MINGW32__ /* windoze version */
291     if( hidden )
292         SetConsoleMode(con.in, HID_INPMODE );
293
294     for(;;) {
295         DWORD nread;
296
297         if( !ReadConsoleA( con.in, cbuf, 1, &nread, NULL ) )
298             log_fatal("ReadConsole failed: rc=%d", (int)GetLastError() );
299         if( !nread )
300             continue;
301         if( *cbuf == '\n' )
302             break;
303
304         if( !hidden )
305             last_prompt_len++;
306         c = *cbuf;
307         if( c == '\t' )
308             c = ' ';
309         else if( c > 0xa0 )
310             ; /* we don't allow 0xa0, as this is a protected blank which may
311                * confuse the user */
312         else if( iscntrl(c) )
313             continue;
314         if( !(i < n-1) ) {
315             n += 50;
316             buf = m_realloc( buf, n );
317         }
318         buf[i++] = c;
319     }
320
321     if( hidden )
322         SetConsoleMode(con.in, DEF_INPMODE );
323
324   #else /* unix version */
325     if( hidden ) {
326       #ifdef HAVE_TCGETATTR
327         struct termios term;
328
329         if( tcgetattr(fileno(ttyfp), &termsave) )
330             log_fatal("tcgetattr() failed: %s\n", strerror(errno) );
331         restore_termios = 1;
332         term = termsave;
333         term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
334         if( tcsetattr( fileno(ttyfp), TCSAFLUSH, &term ) )
335             log_fatal("tcsetattr() failed: %s\n", strerror(errno) );
336       #endif
337     }
338
339     /* fixme: How can we avoid that the \n is echoed w/o disabling
340      * canonical mode - w/o this kill_prompt can't work */
341     while( read(fileno(ttyfp), cbuf, 1) == 1 && *cbuf != '\n' ) {
342         if( !hidden )
343             last_prompt_len++;
344         c = *cbuf;
345         if( c == CONTROL_D )
346             log_info("control d found\n");
347         if( c == '\t' )
348             c = ' ';
349         else if( c > 0xa0 )
350             ; /* we don't allow 0xa0, as this is a protected blank which may
351                * confuse the user */
352         else if( iscntrl(c) )
353             continue;
354         if( !(i < n-1) ) {
355             n += 50;
356             buf = m_realloc( buf, n );
357         }
358         buf[i++] = c;
359     }
360     if( *cbuf != '\n' ) {
361         buf[0] = CONTROL_D;
362         i = 1;
363     }
364
365
366     if( hidden ) {
367       #ifdef HAVE_TCGETATTR
368         if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) )
369             log_error("tcsetattr() failed: %s\n", strerror(errno) );
370         restore_termios = 0;
371       #endif
372     }
373   #endif /* end unix version */
374     buf[i] = 0;
375     return buf;
376 }
377
378
379 char *
380 tty_get( const char *prompt )
381 {
382     return do_get( prompt, 0 );
383 }
384
385 char *
386 tty_get_hidden( const char *prompt )
387 {
388     return do_get( prompt, 1 );
389 }
390
391
392 void
393 tty_kill_prompt()
394 {
395     if ( no_terminal )
396         return;
397
398     if( !initialized )
399         init_ttyfp();
400
401     if( batchmode )
402         last_prompt_len = 0;
403     if( !last_prompt_len )
404         return;
405   #if __MINGW32__
406     tty_printf("\r%*s\r", last_prompt_len, "");
407   #else
408     {
409         int i;
410         putc('\r', ttyfp);
411         for(i=0; i < last_prompt_len; i ++ )
412             putc(' ', ttyfp);
413         putc('\r', ttyfp);
414         fflush(ttyfp);
415     }
416   #endif
417     last_prompt_len = 0;
418 }
419
420
421 int
422 tty_get_answer_is_yes( const char *prompt )
423 {
424     int yes;
425     char *p = tty_get( prompt );
426     tty_kill_prompt();
427     yes = answer_is_yes(p);
428     m_free(p);
429     return yes;
430 }
431