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