*/
#include "libbb.h"
#include "shell_common.h"
-#include <sys/resource.h> /* getrlimit */
const char defifsvar[] ALIGN1 = "IFS= \t\n";
const char defoptindvar[] ALIGN1 = "OPTIND=1";
//Here we can simply store "VAR=" at buffer start and store read data directly
//after "=", then pass buffer to setvar() to consume.
const char* FAST_FUNC
-shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
- char **argv,
- const char *ifs,
- int read_flags,
- const char *opt_n,
- const char *opt_p,
- const char *opt_t,
- const char *opt_u,
- const char *opt_d
-)
+shell_builtin_read(struct builtin_read_params *params)
{
struct pollfd pfd[1];
#define fd (pfd[0].fd) /* -u FD */
int bufpos; /* need to be able to hold -1 */
int startword;
smallint backslash;
+ char **argv;
+ const char *ifs;
+ int read_flags;
errno = err = 0;
+ argv = params->argv;
pp = argv;
while (*pp) {
if (!is_well_formed_var_name(*pp, '\0')) {
}
nchars = 0; /* if != 0, -n is in effect */
- if (opt_n) {
- nchars = bb_strtou(opt_n, NULL, 10);
+ if (params->opt_n) {
+ nchars = bb_strtou(params->opt_n, NULL, 10);
if (nchars < 0 || errno)
return "invalid count";
/* note: "-n 0": off (bash 3.2 does this too) */
}
end_ms = 0;
- if (opt_t && !ENABLE_FEATURE_SH_READ_FRAC) {
- end_ms = bb_strtou(opt_t, NULL, 10);
+ if (params->opt_t && !ENABLE_FEATURE_SH_READ_FRAC) {
+ end_ms = bb_strtou(params->opt_t, NULL, 10);
if (errno)
return "invalid timeout";
if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */
end_ms = UINT_MAX / 2048;
end_ms *= 1000;
}
- if (opt_t && ENABLE_FEATURE_SH_READ_FRAC) {
+ if (params->opt_t && ENABLE_FEATURE_SH_READ_FRAC) {
/* bash 4.3 (maybe earlier) supports -t N.NNNNNN */
char *p;
/* Eat up to three fractional digits */
int frac_digits = 3 + 1;
- end_ms = bb_strtou(opt_t, &p, 10);
+ end_ms = bb_strtou(params->opt_t, &p, 10);
if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */
end_ms = UINT_MAX / 2048;
}
fd = STDIN_FILENO;
- if (opt_u) {
- fd = bb_strtou(opt_u, NULL, 10);
+ if (params->opt_u) {
+ fd = bb_strtou(params->opt_u, NULL, 10);
if (fd < 0 || errno)
return "invalid file descriptor";
}
- if (opt_t && end_ms == 0) {
+ if (params->opt_t && end_ms == 0) {
/* "If timeout is 0, read returns immediately, without trying
* to read any data. The exit status is 0 if input is available
* on the specified file descriptor, non-zero otherwise."
return (const char *)(uintptr_t)(r <= 0);
}
- if (opt_p && isatty(fd)) {
- fputs(opt_p, stderr);
+ if (params->opt_p && isatty(fd)) {
+ fputs(params->opt_p, stderr);
fflush_all();
}
+ ifs = params->ifs;
if (ifs == NULL)
ifs = defifs;
+ read_flags = params->read_flags;
if (nchars || (read_flags & BUILTIN_READ_SILENT)) {
tcgetattr(fd, &tty);
old_tty = tty;
retval = (const char *)(uintptr_t)0;
startword = 1;
backslash = 0;
- if (opt_t)
+ if (params->opt_t)
end_ms += (unsigned)monotonic_ms();
buffer = NULL;
bufpos = 0;
- delim = opt_d ? *opt_d : '\n';
+ delim = params->opt_d ? params->opt_d[0] : '\n';
do {
char c;
int timeout;
buffer = xrealloc(buffer, bufpos + 0x101);
timeout = -1;
- if (opt_t) {
+ if (params->opt_t) {
timeout = end_ms - (unsigned)monotonic_ms();
/* ^^^^^^^^^^^^^ all values are unsigned,
* wrapping math is used here, good even if
* without variable names (bash compat).
* Thus, "read" and "read REPLY" are not the same.
*/
- if (!opt_d && argv[0]) {
+ if (!params->opt_d && argv[0]) {
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */
const char *is_ifs = strchr(ifs, c);
if (startword && is_ifs) {
if (argv[1] != NULL && is_ifs) {
buffer[bufpos] = '\0';
bufpos = 0;
- setvar(*argv, buffer);
+ params->setvar(*argv, buffer);
argv++;
/* can we skip one non-space ifs char? (2: yes) */
startword = isspace(c) ? 2 : 1;
if (argv[0]) {
/* Remove trailing space $IFS chars */
- while (--bufpos >= 0 && isspace(buffer[bufpos]) && strchr(ifs, buffer[bufpos]) != NULL)
+ while (--bufpos >= 0
+ && isspace(buffer[bufpos])
+ && strchr(ifs, buffer[bufpos]) != NULL
+ ) {
continue;
+ }
buffer[bufpos + 1] = '\0';
+
+ /* Last variable takes the entire remainder with delimiters
+ * (sans trailing whitespace $IFS),
+ * but ***only "if there are fewer vars than fields"(c)***!
+ * The "X:Y:" case below: there are two fields,
+ * and therefore last delimiter (:) is eaten:
+ * IFS=": "
+ * echo "X:Y:Z:" | (read x y; echo "|$x|$y|") # |X|Y:Z:|
+ * echo "X:Y:Z" | (read x y; echo "|$x|$y|") # |X|Y:Z|
+ * echo "X:Y:" | (read x y; echo "|$x|$y|") # |X|Y|, not |X|Y:|
+ * echo "X:Y : " | (read x y; echo "|$x|$y|") # |X|Y|
+ */
+ if (bufpos >= 0
+ && strchr(ifs, buffer[bufpos]) != NULL
+ ) {
+ /* There _is_ a non-whitespace IFS char */
+ /* Skip whitespace IFS char before it */
+ while (--bufpos >= 0
+ && isspace(buffer[bufpos])
+ && strchr(ifs, buffer[bufpos]) != NULL
+ ) {
+ continue;
+ }
+ /* Are there $IFS chars? */
+ if (strcspn(buffer, ifs) >= ++bufpos) {
+ /* No: last var takes one field, not more */
+ /* So, drop trailing IFS delims */
+ buffer[bufpos] = '\0';
+ }
+ }
+
/* Use the remainder as a value for the next variable */
- setvar(*argv, buffer);
+ params->setvar(*argv, buffer);
/* Set the rest to "" */
while (*++argv)
- setvar(*argv, "");
+ params->setvar(*argv, "");
} else {
/* Note: no $IFS removal */
buffer[bufpos] = '\0';
- setvar("REPLY", buffer);
+ params->setvar("REPLY", buffer);
}
ret: