blob: 7f667e9c1c451472104f671743788996bd2d370e [file] [log] [blame]
Denys Vlasenko73067272010-01-12 22:11:24 +01001/* 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
23const char* FAST_FUNC
24builtin_read(void (*setvar)(const char *name, const char *val, int flags),
25 char **argv,
26 const char *ifs,
27 int read_flags,
28 const char *opt_n,
29 const char *opt_p,
30 const char *opt_t,
31 const char *opt_u
32)
33{
34 static const char *const arg_REPLY[] = { "REPLY", NULL };
35
36 unsigned end_ms; /* -t TIMEOUT */
37 int fd; /* -u FD */
38 int nchars; /* -n NUM */
39 char *buffer;
40 struct termios tty, old_tty;
41 const char *retval;
42 int bufpos; /* need to be able to hold -1 */
43 int startword;
44 smallint backslash;
45
46 nchars = 0; /* if != 0, -n is in effect */
47 if (opt_n) {
48 nchars = bb_strtou(opt_n, NULL, 10);
49 if (nchars < 0 || errno)
50 return "invalid count";
51 /* note: "-n 0": off (bash 3.2 does this too) */
52 }
53 end_ms = 0;
54 if (opt_t) {
55 end_ms = bb_strtou(opt_t, NULL, 10);
56 if (errno || end_ms > UINT_MAX / 2048)
57 return "invalid timeout";
58 end_ms *= 1000;
59#if 0 /* even bash has no -t N.NNN support */
60 ts.tv_sec = bb_strtou(opt_t, &p, 10);
61 ts.tv_usec = 0;
62 /* EINVAL means number is ok, but not terminated by NUL */
63 if (*p == '.' && errno == EINVAL) {
64 char *p2;
65 if (*++p) {
66 int scale;
67 ts.tv_usec = bb_strtou(p, &p2, 10);
68 if (errno)
69 return "invalid timeout";
70 scale = p2 - p;
71 /* normalize to usec */
72 if (scale > 6)
73 return "invalid timeout";
74 while (scale++ < 6)
75 ts.tv_usec *= 10;
76 }
77 } else if (ts.tv_sec < 0 || errno) {
78 return "invalid timeout";
79 }
80 if (!(ts.tv_sec | ts.tv_usec)) { /* both are 0? */
81 return "invalid timeout";
82 }
83#endif /* if 0 */
84 }
85 fd = STDIN_FILENO;
86 if (opt_u) {
87 fd = bb_strtou(opt_u, NULL, 10);
88 if (fd < 0 || errno)
89 return "invalid file descriptor";
90 }
91
92 if (opt_p && isatty(fd)) {
93 fputs(opt_p, stderr);
94 fflush_all();
95 }
96
97 if (argv[0] == NULL)
98 argv = (char**)arg_REPLY;
99 if (ifs == NULL)
100 ifs = defifs;
101
102 if (nchars || (read_flags & BUILTIN_READ_SILENT)) {
103 tcgetattr(fd, &tty);
104 old_tty = tty;
105 if (nchars) {
106 tty.c_lflag &= ~ICANON;
107 tty.c_cc[VMIN] = nchars < 256 ? nchars : 255;
108 }
109 if (read_flags & BUILTIN_READ_SILENT) {
110 tty.c_lflag &= ~(ECHO | ECHOK | ECHONL);
111 }
112 /* This forces execution of "restoring" tcgetattr later */
113 read_flags |= BUILTIN_READ_SILENT;
114 /* if tcgetattr failed, tcsetattr will fail too.
115 * Ignoring, it's harmless. */
116 tcsetattr(fd, TCSANOW, &tty);
117 }
118
119 retval = (const char *)(uintptr_t)0;
120 startword = 1;
121 backslash = 0;
122 if (end_ms) /* NB: end_ms stays nonzero: */
123 end_ms = ((unsigned)monotonic_ms() + end_ms) | 1;
124 buffer = NULL;
125 bufpos = 0;
126 do {
127 char c;
128 const char *is_ifs;
129
130 if (end_ms) {
131 int timeout;
132 struct pollfd pfd[1];
133
134 pfd[0].fd = fd;
135 pfd[0].events = POLLIN;
136 timeout = end_ms - (unsigned)monotonic_ms();
137 if (timeout <= 0 /* already late? */
138 || safe_poll(pfd, 1, timeout) != 1 /* no? wait... */
139 ) { /* timed out! */
140 retval = (const char *)(uintptr_t)1;
141 goto ret;
142 }
143 }
144
145 if ((bufpos & 0xff) == 0)
146 buffer = xrealloc(buffer, bufpos + 0x100);
147 if (nonblock_safe_read(fd, &buffer[bufpos], 1) != 1) {
148 retval = (const char *)(uintptr_t)1;
149 break;
150 }
151 c = buffer[bufpos];
152 if (c == '\0')
153 continue;
154 if (backslash) {
155 backslash = 0;
156 if (c != '\n')
157 goto put;
158 continue;
159 }
160 if (!(read_flags & BUILTIN_READ_RAW) && c == '\\') {
161 backslash = 1;
162 continue;
163 }
164 if (c == '\n')
165 break;
166 /* $IFS splitting */
167/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */
168 is_ifs = strchr(ifs, c);
169 if (startword && is_ifs) {
170 if (isspace(c))
171 continue;
172 /* it is a non-space ifs char */
173 startword--;
174 if (startword == 1) /* first one? */
175 continue; /* yes, it is not next word yet */
176 }
177 startword = 0;
178 if (argv[1] != NULL && is_ifs) {
179 buffer[bufpos] = '\0';
180 bufpos = 0;
181 setvar(*argv, buffer, 0);
182 argv++;
183 /* can we skip one non-space ifs char? (2: yes) */
184 startword = isspace(c) ? 2 : 1;
185 continue;
186 }
187 put:
188 bufpos++;
189 } while (--nchars);
190
191 /* Remove trailing space ifs chars */
192 while (--bufpos >= 0 && isspace(buffer[bufpos]) && strchr(ifs, buffer[bufpos]) != NULL)
193 continue;
194 buffer[bufpos + 1] = '\0';
195
196 setvar(*argv, buffer, 0);
197
198 while (*++argv != NULL)
199 setvar(*argv, "", 0);
200 ret:
201 free(buffer);
202 if (read_flags & BUILTIN_READ_SILENT)
203 tcsetattr(fd, TCSANOW, &old_tty);
204 return retval;
205}