* getpass.c (fflush_unlocked, flockfile, funlockfile)
[gnulib.git] / lib / getpass.c
1 /* Copyright (C) 1992-2001, 2003, 2004 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License along
15    with this program; if not, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 #if HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 #if !_LIBC
23 # include "getpass.h"
24 #endif
25
26 #if _LIBC
27 # define HAVE_STDIO_EXT_H 1
28 #endif
29
30 #include <stdbool.h>
31
32 #include <stdio.h>
33 #if HAVE_STDIO_EXT_H
34 # include <stdio_ext.h>
35 #else
36 # define __fsetlocking(stream, type) /* empty */
37 #endif
38 #if !_LIBC
39 # include "getline.h"
40 #endif
41
42 #include <termios.h>
43 #include <unistd.h>
44
45 #if _LIBC
46 # include <wchar.h>
47 #endif
48
49 #if _LIBC
50 # define NOTCANCEL_MODE "c"
51 #else
52 # define NOTCANCEL_MODE
53 #endif
54
55 #if _LIBC
56 # define flockfile(s) _IO_flockfile (s)
57 # define funlockfile(s) _IO_funlockfile (s)
58 #elif USE_UNLOCKED_IO
59 # include "unlocked-io.h"
60 #else
61 # undef fflush_unlocked
62 # define fflush_unlocked(x) fflush (x)
63 # undef flockfile
64 # define flockfile(x) ((void) 0)
65 # undef funlockfile
66 # define funlockfile(x) ((void) 0)
67 # undef fputs_unlocked
68 # define fputs_unlocked(str,stream) fputs (str, stream)
69 # undef putc_unlocked
70 # define putc_unlocked(c,stream) putc (c, stream)
71 #endif
72
73 #if _LIBC
74 # include <bits/libc-lock.h>
75 #else
76 # define __libc_cleanup_push(function, arg) /* empty */
77 # define __libc_cleanup_pop(execute) /* empty */
78 #endif
79
80 #if !_LIBC
81 # define __getline getline
82 # define __tcgetattr tcgetattr
83 #endif
84
85 /* It is desirable to use this bit on systems that have it.
86    The only bit of terminal state we want to twiddle is echoing, which is
87    done in software; there is no need to change the state of the terminal
88    hardware.  */
89
90 #ifndef TCSASOFT
91 # define TCSASOFT 0
92 #endif
93
94 static void
95 call_fclose (void *arg)
96 {
97   if (arg != NULL)
98     fclose (arg);
99 }
100
101 char *
102 getpass (const char *prompt)
103 {
104   FILE *tty;
105   FILE *in, *out;
106   struct termios s, t;
107   bool tty_changed;
108   static char *buf;
109   static size_t bufsize;
110   ssize_t nread;
111
112   /* Try to write to and read from the terminal if we can.
113      If we can't open the terminal, use stderr and stdin.  */
114
115   tty = fopen ("/dev/tty", "w+" NOTCANCEL_MODE);
116   if (tty == NULL)
117     {
118       in = stdin;
119       out = stderr;
120     }
121   else
122     {
123       /* We do the locking ourselves.  */
124       __fsetlocking (tty, FSETLOCKING_BYCALLER);
125
126       out = in = tty;
127     }
128
129   /* Make sure the stream we opened is closed even if the thread is
130      canceled.  */
131   __libc_cleanup_push (call_fclose, tty);
132
133   flockfile (out);
134
135   /* Turn echoing off if it is on now.  */
136
137   if (__tcgetattr (fileno (in), &t) == 0)
138     {
139       /* Save the old one. */
140       s = t;
141       /* Tricky, tricky. */
142       t.c_lflag &= ~(ECHO|ISIG);
143       tty_changed = (tcsetattr (fileno (in), TCSAFLUSH|TCSASOFT, &t) == 0);
144     }
145   else
146     tty_changed = false;
147
148   /* Write the prompt.  */
149 #ifdef USE_IN_LIBIO
150   if (_IO_fwide (out, 0) > 0)
151     __fwprintf (out, L"%s", prompt);
152   else
153 #endif
154     fputs_unlocked (prompt, out);
155   fflush_unlocked (out);
156
157   /* Read the password.  */
158   nread = __getline (&buf, &bufsize, in);
159
160 #if !_LIBC
161   /* As far as is known, glibc doesn't need this no-op fseek.  */
162
163   /* According to the C standard, input may not be followed by output
164      on the same stream without an intervening call to a file
165      positioning function.  Suppose in == out; then without this fseek
166      call, on Solaris, HP-UX, AIX, OSF/1, the previous input gets
167      echoed, whereas on IRIX, the following newline is not output as
168      it should be.  POSIX imposes similar restrictions if fileno (in)
169      == fileno (out).  The POSIX restrictions are tricky and change
170      from POSIX version to POSIX version, so play it safe and invoke
171      fseek even if in != out.  */
172   fseek (out, 0, SEEK_CUR);
173 #endif
174
175   if (buf != NULL)
176     {
177       if (nread < 0)
178         buf[0] = '\0';
179       else if (buf[nread - 1] == '\n')
180         {
181           /* Remove the newline.  */
182           buf[nread - 1] = '\0';
183           if (tty_changed)
184             {
185               /* Write the newline that was not echoed.  */
186 #ifdef USE_IN_LIBIO
187               if (_IO_fwide (out, 0) > 0)
188                 putwc_unlocked (L'\n', out);
189               else
190 #endif
191                 putc_unlocked ('\n', out);
192             }
193         }
194     }
195
196   /* Restore the original setting.  */
197   if (tty_changed)
198     (void) tcsetattr (fileno (in), TCSAFLUSH|TCSASOFT, &s);
199
200   funlockfile (out);
201
202   __libc_cleanup_pop (0);
203
204   call_fclose (tty);
205
206   return buf;
207 }