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