Remove dependencies on unlocked-io.
[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 #endif
61
62 #if _LIBC
63 # include <bits/libc-lock.h>
64 #else
65 # define __libc_cleanup_push(function, arg) /* empty */
66 # define __libc_cleanup_pop(execute) /* empty */
67 #endif
68
69 #if !_LIBC
70 # define __getline getline
71 # define __tcgetattr tcgetattr
72 #endif
73
74 /* It is desirable to use this bit on systems that have it.
75    The only bit of terminal state we want to twiddle is echoing, which is
76    done in software; there is no need to change the state of the terminal
77    hardware.  */
78
79 #ifndef TCSASOFT
80 # define TCSASOFT 0
81 #endif
82
83 static void
84 call_fclose (void *arg)
85 {
86   if (arg != NULL)
87     fclose (arg);
88 }
89
90 char *
91 getpass (const char *prompt)
92 {
93   FILE *tty;
94   FILE *in, *out;
95   struct termios s, t;
96   bool tty_changed;
97   static char *buf;
98   static size_t bufsize;
99   ssize_t nread;
100
101   /* Try to write to and read from the terminal if we can.
102      If we can't open the terminal, use stderr and stdin.  */
103
104   tty = fopen ("/dev/tty", "w+" NOTCANCEL_MODE);
105   if (tty == NULL)
106     {
107       in = stdin;
108       out = stderr;
109     }
110   else
111     {
112       /* We do the locking ourselves.  */
113       __fsetlocking (tty, FSETLOCKING_BYCALLER);
114
115       out = in = tty;
116     }
117
118   /* Make sure the stream we opened is closed even if the thread is
119      canceled.  */
120   __libc_cleanup_push (call_fclose, tty);
121
122   flockfile (out);
123
124   /* Turn echoing off if it is on now.  */
125
126   if (__tcgetattr (fileno (in), &t) == 0)
127     {
128       /* Save the old one. */
129       s = t;
130       /* Tricky, tricky. */
131       t.c_lflag &= ~(ECHO|ISIG);
132       tty_changed = (tcsetattr (fileno (in), TCSAFLUSH|TCSASOFT, &t) == 0);
133     }
134   else
135     tty_changed = false;
136
137   /* Write the prompt.  */
138 #ifdef USE_IN_LIBIO
139   if (_IO_fwide (out, 0) > 0)
140     __fwprintf (out, L"%s", prompt);
141   else
142 #endif
143     fputs_unlocked (prompt, out);
144   fflush_unlocked (out);
145
146   /* Read the password.  */
147   nread = __getline (&buf, &bufsize, in);
148
149 #if !_LIBC
150   /* As far as is known, glibc doesn't need this no-op fseek.  */
151
152   /* According to the C standard, input may not be followed by output
153      on the same stream without an intervening call to a file
154      positioning function.  Suppose in == out; then without this fseek
155      call, on Solaris, HP-UX, AIX, OSF/1, the previous input gets
156      echoed, whereas on IRIX, the following newline is not output as
157      it should be.  POSIX imposes similar restrictions if fileno (in)
158      == fileno (out).  The POSIX restrictions are tricky and change
159      from POSIX version to POSIX version, so play it safe and invoke
160      fseek even if in != out.  */
161   fseek (out, 0, SEEK_CUR);
162 #endif
163
164   if (buf != NULL)
165     {
166       if (nread < 0)
167         buf[0] = '\0';
168       else if (buf[nread - 1] == '\n')
169         {
170           /* Remove the newline.  */
171           buf[nread - 1] = '\0';
172           if (tty_changed)
173             {
174               /* Write the newline that was not echoed.  */
175 #ifdef USE_IN_LIBIO
176               if (_IO_fwide (out, 0) > 0)
177                 putwc_unlocked (L'\n', out);
178               else
179 #endif
180                 putc_unlocked ('\n', out);
181             }
182         }
183     }
184
185   /* Restore the original setting.  */
186   if (tty_changed)
187     (void) tcsetattr (fileno (in), TCSAFLUSH|TCSASOFT, &s);
188
189   funlockfile (out);
190
191   __libc_cleanup_pop (0);
192
193   call_fclose (tty);
194
195   return buf;
196 }