whitespace fixes
[oweals/busybox.git] / shell / builtin_read.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Adapted from ash applet code
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Kenneth Almquist.
7  *
8  * Copyright (c) 1989, 1991, 1993, 1994
9  *      The Regents of the University of California.  All rights reserved.
10  *
11  * Copyright (c) 1997-2005 Herbert Xu <herbert@gondor.apana.org.au>
12  * was re-ported from NetBSD and debianized.
13  *
14  * Copyright (c) 2010 Denys Vlasenko
15  * Split from ash.c
16  *
17  * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
18  */
19 #include "libbb.h"
20 #include "shell_common.h"
21 #include "builtin_read.h"
22
23 //TODO: use more efficient setvar() which takes a pointer to malloced "VAR=VAL"
24 //string. hush naturally has it, and ash has setvareq().
25 //Here we can simply store "VAR=" at buffer start and store read data directly
26 //after "=", then pass buffer to setvar() to consume.
27
28 const char* FAST_FUNC
29 shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
30         char       **argv,
31         const char *ifs,
32         int        read_flags,
33         const char *opt_n,
34         const char *opt_p,
35         const char *opt_t,
36         const char *opt_u
37 )
38 {
39         unsigned end_ms; /* -t TIMEOUT */
40         int fd; /* -u FD */
41         int nchars; /* -n NUM */
42         char **pp;
43         char *buffer;
44         struct termios tty, old_tty;
45         const char *retval;
46         int bufpos; /* need to be able to hold -1 */
47         int startword;
48         smallint backslash;
49
50         pp = argv;
51         while (*pp) {
52                 if (!is_well_formed_var_name(*pp, '\0')) {
53                         /* Mimic bash message */
54                         bb_error_msg("read: '%s': not a valid identifier", *pp);
55                         return (const char *)(uintptr_t)1;
56                 }
57                 pp++;
58         }
59
60         nchars = 0; /* if != 0, -n is in effect */
61         if (opt_n) {
62                 nchars = bb_strtou(opt_n, NULL, 10);
63                 if (nchars < 0 || errno)
64                         return "invalid count";
65                 /* note: "-n 0": off (bash 3.2 does this too) */
66         }
67         end_ms = 0;
68         if (opt_t) {
69                 end_ms = bb_strtou(opt_t, NULL, 10);
70                 if (errno || end_ms > UINT_MAX / 2048)
71                         return "invalid timeout";
72                 end_ms *= 1000;
73 #if 0 /* even bash has no -t N.NNN support */
74                 ts.tv_sec = bb_strtou(opt_t, &p, 10);
75                 ts.tv_usec = 0;
76                 /* EINVAL means number is ok, but not terminated by NUL */
77                 if (*p == '.' && errno == EINVAL) {
78                         char *p2;
79                         if (*++p) {
80                                 int scale;
81                                 ts.tv_usec = bb_strtou(p, &p2, 10);
82                                 if (errno)
83                                         return "invalid timeout";
84                                 scale = p2 - p;
85                                 /* normalize to usec */
86                                 if (scale > 6)
87                                         return "invalid timeout";
88                                 while (scale++ < 6)
89                                         ts.tv_usec *= 10;
90                         }
91                 } else if (ts.tv_sec < 0 || errno) {
92                         return "invalid timeout";
93                 }
94                 if (!(ts.tv_sec | ts.tv_usec)) { /* both are 0? */
95                         return "invalid timeout";
96                 }
97 #endif /* if 0 */
98         }
99         fd = STDIN_FILENO;
100         if (opt_u) {
101                 fd = bb_strtou(opt_u, NULL, 10);
102                 if (fd < 0 || errno)
103                         return "invalid file descriptor";
104         }
105
106         if (opt_p && isatty(fd)) {
107                 fputs(opt_p, stderr);
108                 fflush_all();
109         }
110
111         if (ifs == NULL)
112                 ifs = defifs;
113
114         if (nchars || (read_flags & BUILTIN_READ_SILENT)) {
115                 tcgetattr(fd, &tty);
116                 old_tty = tty;
117                 if (nchars) {
118                         tty.c_lflag &= ~ICANON;
119                         tty.c_cc[VMIN] = nchars < 256 ? nchars : 255;
120                 }
121                 if (read_flags & BUILTIN_READ_SILENT) {
122                         tty.c_lflag &= ~(ECHO | ECHOK | ECHONL);
123                 }
124                 /* This forces execution of "restoring" tcgetattr later */
125                 read_flags |= BUILTIN_READ_SILENT;
126                 /* if tcgetattr failed, tcsetattr will fail too.
127                  * Ignoring, it's harmless. */
128                 tcsetattr(fd, TCSANOW, &tty);
129         }
130
131         retval = (const char *)(uintptr_t)0;
132         startword = 1;
133         backslash = 0;
134         if (end_ms) /* NB: end_ms stays nonzero: */
135                 end_ms = ((unsigned)monotonic_ms() + end_ms) | 1;
136         buffer = NULL;
137         bufpos = 0;
138         do {
139                 char c;
140
141                 if (end_ms) {
142                         int timeout;
143                         struct pollfd pfd[1];
144
145                         pfd[0].fd = fd;
146                         pfd[0].events = POLLIN;
147                         timeout = end_ms - (unsigned)monotonic_ms();
148                         if (timeout <= 0 /* already late? */
149                          || safe_poll(pfd, 1, timeout) != 1 /* no? wait... */
150                         ) { /* timed out! */
151                                 retval = (const char *)(uintptr_t)1;
152                                 goto ret;
153                         }
154                 }
155
156                 if ((bufpos & 0xff) == 0)
157                         buffer = xrealloc(buffer, bufpos + 0x100);
158                 if (nonblock_safe_read(fd, &buffer[bufpos], 1) != 1) {
159                         retval = (const char *)(uintptr_t)1;
160                         break;
161                 }
162                 c = buffer[bufpos];
163                 if (c == '\0')
164                         continue;
165                 if (backslash) {
166                         backslash = 0;
167                         if (c != '\n')
168                                 goto put;
169                         continue;
170                 }
171                 if (!(read_flags & BUILTIN_READ_RAW) && c == '\\') {
172                         backslash = 1;
173                         continue;
174                 }
175                 if (c == '\n')
176                         break;
177
178                 /* $IFS splitting. NOT done if we run "read"
179                  * without variable names (bash compat).
180                  * Thus, "read" and "read REPLY" are not the same.
181                  */
182                 if (argv[0]) {
183 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */
184                         const char *is_ifs = strchr(ifs, c);
185                         if (startword && is_ifs) {
186                                 if (isspace(c))
187                                         continue;
188                                 /* it is a non-space ifs char */
189                                 startword--;
190                                 if (startword == 1) /* first one? */
191                                         continue; /* yes, it is not next word yet */
192                         }
193                         startword = 0;
194                         if (argv[1] != NULL && is_ifs) {
195                                 buffer[bufpos] = '\0';
196                                 bufpos = 0;
197                                 setvar(*argv, buffer);
198                                 argv++;
199                                 /* can we skip one non-space ifs char? (2: yes) */
200                                 startword = isspace(c) ? 2 : 1;
201                                 continue;
202                         }
203                 }
204  put:
205                 bufpos++;
206         } while (--nchars);
207
208         if (argv[0]) {
209                 /* Remove trailing space $IFS chars */
210                 while (--bufpos >= 0 && isspace(buffer[bufpos]) && strchr(ifs, buffer[bufpos]) != NULL)
211                         continue;
212                 buffer[bufpos + 1] = '\0';
213                 /* Use the remainder as a value for the next variable */
214                 setvar(*argv, buffer);
215                 /* Set the rest to "" */
216                 while (*++argv)
217                         setvar(*argv, "");
218         } else {
219                 /* Note: no $IFS removal */
220                 buffer[bufpos] = '\0';
221                 setvar("REPLY", buffer);
222         }
223
224  ret:
225         free(buffer);
226         if (read_flags & BUILTIN_READ_SILENT)
227                 tcsetattr(fd, TCSANOW, &old_tty);
228         return retval;
229 }