diff options
Diffstat (limited to 'shell/builtin_read.c')
-rw-r--r-- | shell/builtin_read.c | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/shell/builtin_read.c b/shell/builtin_read.c new file mode 100644 index 000000000..7f667e9c1 --- /dev/null +++ b/shell/builtin_read.c | |||
@@ -0,0 +1,205 @@ | |||
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 | const char* FAST_FUNC | ||
24 | builtin_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 | } | ||