1 /* prompt.c -- prompt routines for vlock,
2  *             the VT locking program for linux
3  *
4  * This program is copyright (C) 2007 Frank Benkstein, and is free
5  * software which is freely distributable under the terms of the
6  * GNU General Public License version 2, included as the file COPYING in this
7  * distribution.  It is NOT public domain software, and any
8  * redistribution not permitted by the GNU General Public License is
9  * expressly forbidden without prior written permission from
10  * the author.
11  *
12  *
13  * The prompt functions (prompt and prompt_echo_off) were
14  * inspired by/copied from openpam's openpam_ttyconv.c:
15  *
16  * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
17  *
18  * Redistribution and use in source and binary forms, with or without
19  * modification, are permitted provided that the following conditions
20  * are met:
21  * 1. Redistributions of source code must retain the above copyright
22  *    notice, this list of conditions and the following disclaimer.
23  * 2. Redistributions in binary form must reproduce the above copyright
24  *    notice, this list of conditions and the following disclaimer in the
25  *    documentation and/or other materials provided with the distribution.
26  * 3. The name of the author may not be used to endorse or promote
27  *    products derived from this software without specific prior written
28  *    permission.
29  *
30  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
31  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
34  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40  * SUCH DAMAGE.
41  *
42  */
43 
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <termios.h>
48 #include <unistd.h>
49 #include <sys/select.h>
50 #include <errno.h>
51 
52 #include "prompt.h"
53 
54 #define PROMPT_BUFFER_SIZE 512
55 
56 /* Prompt with the given string for a single line of input.  The read string is
57  * returned in a new buffer that should be freed by the caller.  If reading
58  * fails or the timeout (if given) occurs NULL is retured. */
prompt(const char * msg,const struct timespec * timeout)59 char *prompt(const char *msg, const struct timespec *timeout)
60 {
61   char buffer[PROMPT_BUFFER_SIZE];
62   char *result = NULL;
63   ssize_t len;
64   struct termios term;
65   struct timeval *timeout_val = NULL;
66   tcflag_t lflag;
67   fd_set readfds;
68 
69   if (msg != NULL) {
70     /* Write out the prompt. */
71     (void) fputs(msg, stderr);
72     fflush(stderr);
73   }
74 
75   /* Get the current terminal attributes. */
76   (void) tcgetattr(STDIN_FILENO, &term);
77   /* Save the lflag value. */
78   lflag = term.c_lflag;
79   /* Enable canonical mode.  We're only interested in line buffering. */
80   term.c_lflag |= ICANON;
81   /* Disable terminal signals. */
82   term.c_lflag &= ~ISIG;
83   /* Set the terminal attributes. */
84   (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &term);
85   /* Discard all unread input characters. */
86   (void) tcflush(STDIN_FILENO, TCIFLUSH);
87 
88   /* Initialize file descriptor set. */
89   FD_ZERO(&readfds);
90   FD_SET(STDIN_FILENO, &readfds);
91 
92 
93 before_select:
94   /* copy timeout */
95   if (timeout != NULL) {
96     timeout_val = malloc(sizeof *timeout_val);
97 
98     if (timeout_val == NULL)
99       return NULL;
100 
101     timeout_val->tv_sec = timeout->tv_sec;
102     timeout_val->tv_usec = timeout->tv_nsec / 1000;
103   }
104 
105   /* Reset errno. */
106   errno = 0;
107 
108   /* Wait until a string was entered. */
109   if (select(STDIN_FILENO + 1, &readfds, NULL, NULL, timeout_val) != 1) {
110     switch (errno) {
111       case 0:
112         fprintf(stderr, "timeout!\n");
113         goto out;
114       case EINTR:
115         /* A signal was caught. Restart. */
116         goto before_select;
117       default:
118         perror("vlock: select() on stdin failed");
119         goto out;
120     }
121   }
122 
123   /* Read the string from stdin.  At most buffer length - 1 bytes, to
124    * leave room for the terminating zero byte. */
125   if ((len = read(STDIN_FILENO, buffer, sizeof buffer - 1)) < 0)
126     goto out;
127 
128   /* Terminate the string. */
129   buffer[len] = '\0';
130 
131   /* Strip trailing newline characters. */
132   for (len = strlen(buffer); len > 0; --len)
133     if (buffer[len - 1] != '\r' && buffer[len - 1] != '\n')
134       break;
135 
136   /* Terminate the string, again. */
137   buffer[len] = '\0';
138 
139   /* Copy the string.  Success and error paths are the same. */
140   result = strdup(buffer);
141 
142   /* Clear our buffer. */
143   memset(buffer, 0, sizeof buffer);
144 
145 out:
146   free(timeout_val);
147 
148   /* Restore original terminal attributes. */
149   term.c_lflag = lflag;
150   (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &term);
151 
152   return result;
153 }
154 
155 /* Same as prompt except that the characters entered are not echoed. */
prompt_echo_off(const char * msg,const struct timespec * timeout)156 char *prompt_echo_off(const char *msg, const struct timespec *timeout)
157 {
158   struct termios term;
159   tcflag_t lflag;
160   char *result;
161 
162   (void) tcgetattr(STDIN_FILENO, &term);
163   lflag = term.c_lflag;
164   term.c_lflag &= ~ECHO;
165   (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &term);
166 
167   result = prompt(msg, timeout);
168 
169   term.c_lflag = lflag;
170   (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &term);
171 
172   if (result != NULL)
173     fputc('\n', stderr);
174 
175   return result;
176 }
177 
178 /* Read a single character from the stdin.  If the timeout is reached
179  * 0 is returned. */
read_character(struct timespec * timeout)180 char read_character(struct timespec *timeout)
181 {
182   char c = 0;
183   struct timeval *timeout_val = NULL;
184   fd_set readfds;
185 
186   if (timeout != NULL) {
187     timeout_val = calloc(sizeof *timeout_val, 1);
188 
189     if (timeout_val != NULL) {
190       timeout_val->tv_sec = timeout->tv_sec;
191       timeout_val->tv_usec = timeout->tv_nsec / 1000;
192     }
193   }
194 
195   /* Initialize file descriptor set. */
196   FD_ZERO(&readfds);
197   FD_SET(STDIN_FILENO, &readfds);
198 
199   /* Wait for a character. */
200   if (select(STDIN_FILENO + 1, &readfds, NULL, NULL, timeout_val) != 1)
201     goto out;
202 
203   /* Read the character. */
204   (void) read(STDIN_FILENO, &c, 1);
205 
206 out:
207   free(timeout_val);
208   return c;
209 }
210 
211 /* Wait for any of the characters in the given character set to be read from
212  * stdin.  If charset is NULL wait for any character.  Returns 0 when the
213  * timeout occurs. */
wait_for_character(const char * charset,struct timespec * timeout)214 char wait_for_character(const char *charset, struct timespec *timeout)
215 {
216   struct termios term;
217   tcflag_t lflag;
218   char c;
219 
220   /* switch off line buffering */
221   (void) tcgetattr(STDIN_FILENO, &term);
222   lflag = term.c_lflag;
223   term.c_lflag &= ~ICANON;
224   (void) tcsetattr(STDIN_FILENO, TCSANOW, &term);
225 
226   for (;;) {
227     c = read_character(timeout);
228 
229     if (c == 0 || charset == NULL)
230       break;
231     else if (strchr(charset, c) != NULL)
232       break;
233   }
234 
235   /* restore line buffering */
236   term.c_lflag = lflag;
237   (void) tcsetattr(STDIN_FILENO, TCSANOW, &term);
238 
239   return c;
240 }
241 
242