hush: make read builtin interruptible.
function old new delta builtin_read 185 471 +286 check_and_run_traps 200 262 +62 nonblock_immune_read 73 119 +46 sigismember - 44 +44 record_signal - 21 +21 sigisemptyset - 16 +16 ... ------------------------------------------------------------------------------ (add/remove: 5/0 grow/shrink: 7/5 up/down: 483/-46) Total: 437 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
parent
80c5b6893d
commit
80542bad2f
@ -672,7 +672,7 @@ void* xrealloc_vector_helper(void *vector, unsigned sizeof_and_shift, int idx) F
|
|||||||
|
|
||||||
|
|
||||||
extern ssize_t safe_read(int fd, void *buf, size_t count) FAST_FUNC;
|
extern ssize_t safe_read(int fd, void *buf, size_t count) FAST_FUNC;
|
||||||
extern ssize_t nonblock_immune_read(int fd, void *buf, size_t count) FAST_FUNC;
|
extern ssize_t nonblock_immune_read(int fd, void *buf, size_t count, int loop_on_EINTR) FAST_FUNC;
|
||||||
// NB: will return short read on error, not -1,
|
// NB: will return short read on error, not -1,
|
||||||
// if some data was read before error occurred
|
// if some data was read before error occurred
|
||||||
extern ssize_t full_read(int fd, void *buf, size_t count) FAST_FUNC;
|
extern ssize_t full_read(int fd, void *buf, size_t count) FAST_FUNC;
|
||||||
|
@ -55,19 +55,20 @@
|
|||||||
* which detects EAGAIN and uses poll() to wait on the fd.
|
* which detects EAGAIN and uses poll() to wait on the fd.
|
||||||
* Thankfully, poll() doesn't care about O_NONBLOCK flag.
|
* Thankfully, poll() doesn't care about O_NONBLOCK flag.
|
||||||
*/
|
*/
|
||||||
ssize_t FAST_FUNC nonblock_immune_read(int fd, void *buf, size_t count)
|
ssize_t FAST_FUNC nonblock_immune_read(int fd, void *buf, size_t count, int loop_on_EINTR)
|
||||||
{
|
{
|
||||||
struct pollfd pfd[1];
|
struct pollfd pfd[1];
|
||||||
ssize_t n;
|
ssize_t n;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
n = safe_read(fd, buf, count);
|
n = loop_on_EINTR ? safe_read(fd, buf, count) : read(fd, buf, count);
|
||||||
if (n >= 0 || errno != EAGAIN)
|
if (n >= 0 || errno != EAGAIN)
|
||||||
return n;
|
return n;
|
||||||
/* fd is in O_NONBLOCK mode. Wait using poll and repeat */
|
/* fd is in O_NONBLOCK mode. Wait using poll and repeat */
|
||||||
pfd[0].fd = fd;
|
pfd[0].fd = fd;
|
||||||
pfd[0].events = POLLIN;
|
pfd[0].events = POLLIN;
|
||||||
safe_poll(pfd, 1, -1); /* note: this pulls in printf */
|
/* note: safe_poll pulls in printf */
|
||||||
|
loop_on_EINTR ? safe_poll(pfd, 1, -1) : poll(pfd, 1, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +91,7 @@ char* FAST_FUNC xmalloc_reads(int fd, size_t *maxsz_p)
|
|||||||
p = buf + sz;
|
p = buf + sz;
|
||||||
sz += 128;
|
sz += 128;
|
||||||
}
|
}
|
||||||
if (nonblock_immune_read(fd, p, 1) != 1) {
|
if (nonblock_immune_read(fd, p, 1, /*loop_on_EINTR:*/ 1) != 1) {
|
||||||
/* EOF/error */
|
/* EOF/error */
|
||||||
if (p == buf) { /* we read nothing */
|
if (p == buf) { /* we read nothing */
|
||||||
free(buf);
|
free(buf);
|
||||||
|
@ -5918,7 +5918,7 @@ expbackq(union node *cmd, int quoted, int quotes)
|
|||||||
read:
|
read:
|
||||||
if (in.fd < 0)
|
if (in.fd < 0)
|
||||||
break;
|
break;
|
||||||
i = nonblock_immune_read(in.fd, buf, sizeof(buf));
|
i = nonblock_immune_read(in.fd, buf, sizeof(buf), /*loop_on_EINTR:*/ 1);
|
||||||
TRACE(("expbackq: read returns %d\n", i));
|
TRACE(("expbackq: read returns %d\n", i));
|
||||||
if (i <= 0)
|
if (i <= 0)
|
||||||
break;
|
break;
|
||||||
@ -9617,7 +9617,7 @@ preadfd(void)
|
|||||||
#if ENABLE_FEATURE_EDITING
|
#if ENABLE_FEATURE_EDITING
|
||||||
retry:
|
retry:
|
||||||
if (!iflag || g_parsefile->pf_fd != STDIN_FILENO)
|
if (!iflag || g_parsefile->pf_fd != STDIN_FILENO)
|
||||||
nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1);
|
nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1, /*loop_on_EINTR:*/ 1);
|
||||||
else {
|
else {
|
||||||
int timeout = -1;
|
int timeout = -1;
|
||||||
# if ENABLE_ASH_IDLE_TIMEOUT
|
# if ENABLE_ASH_IDLE_TIMEOUT
|
||||||
@ -9663,7 +9663,7 @@ preadfd(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1);
|
nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1, /*loop_on_EINTR:*/ 1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if 0 /* disabled: nonblock_immune_read() handles this problem */
|
#if 0 /* disabled: nonblock_immune_read() handles this problem */
|
||||||
|
98
shell/hush.c
98
shell/hush.c
@ -795,8 +795,15 @@ struct globals {
|
|||||||
/* which signals have non-DFL handler (even with no traps set)? */
|
/* which signals have non-DFL handler (even with no traps set)? */
|
||||||
unsigned non_DFL_mask;
|
unsigned non_DFL_mask;
|
||||||
char **traps; /* char *traps[NSIG] */
|
char **traps; /* char *traps[NSIG] */
|
||||||
sigset_t blocked_set;
|
/* Signal mask on the entry to the (top-level) shell. Never modified. */
|
||||||
sigset_t inherited_set;
|
sigset_t inherited_set;
|
||||||
|
/* Starts equal to inherited_set,
|
||||||
|
* but shell-special signals are added and SIGCHLD is removed.
|
||||||
|
* When a trap is set/cleared, signal is added to/removed from it:
|
||||||
|
*/
|
||||||
|
sigset_t blocked_set;
|
||||||
|
/* Used by read() */
|
||||||
|
sigset_t detected_set;
|
||||||
#if HUSH_DEBUG
|
#if HUSH_DEBUG
|
||||||
unsigned long memleak_value;
|
unsigned long memleak_value;
|
||||||
int debug_indent;
|
int debug_indent;
|
||||||
@ -1476,6 +1483,17 @@ static int check_and_run_traps(int sig)
|
|||||||
goto got_sig;
|
goto got_sig;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
|
if (!sigisemptyset(&G.detected_set)) {
|
||||||
|
sig = 0;
|
||||||
|
do {
|
||||||
|
sig++;
|
||||||
|
if (sigismember(&G.detected_set, sig)) {
|
||||||
|
sigdelset(&G.detected_set, sig);
|
||||||
|
goto got_sig;
|
||||||
|
}
|
||||||
|
} while (sig < NSIG);
|
||||||
|
}
|
||||||
|
|
||||||
sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec);
|
sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec);
|
||||||
if (sig <= 0)
|
if (sig <= 0)
|
||||||
break;
|
break;
|
||||||
@ -8484,6 +8502,32 @@ static int FAST_FUNC builtin_pwd(char **argv UNUSED_PARAM)
|
|||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Interruptibility of read builtin in bash
|
||||||
|
* (tested on bash-4.2.8 by sending signals (not by ^C)):
|
||||||
|
*
|
||||||
|
* Empty trap makes read ignore corresponding signal, for any signal.
|
||||||
|
*
|
||||||
|
* SIGINT:
|
||||||
|
* - terminates non-interactive shell;
|
||||||
|
* - interrupts read in interactive shell;
|
||||||
|
* if it has non-empty trap:
|
||||||
|
* - executes trap and returns to command prompt in interactive shell;
|
||||||
|
* - executes trap and returns to read in non-interactive shell;
|
||||||
|
* SIGTERM:
|
||||||
|
* - is ignored (does not interrupt) read in interactive shell;
|
||||||
|
* - terminates non-interactive shell;
|
||||||
|
* if it has non-empty trap:
|
||||||
|
* - executes trap and returns to read;
|
||||||
|
* SIGHUP:
|
||||||
|
* - terminates shell (regardless of interactivity);
|
||||||
|
* if it has non-empty trap:
|
||||||
|
* - executes trap and returns to read;
|
||||||
|
*/
|
||||||
|
/* helper */
|
||||||
|
static void record_signal(int sig)
|
||||||
|
{
|
||||||
|
sigaddset(&G.detected_set, sig);
|
||||||
|
}
|
||||||
static int FAST_FUNC builtin_read(char **argv)
|
static int FAST_FUNC builtin_read(char **argv)
|
||||||
{
|
{
|
||||||
const char *r;
|
const char *r;
|
||||||
@ -8491,7 +8535,9 @@ static int FAST_FUNC builtin_read(char **argv)
|
|||||||
char *opt_p = NULL;
|
char *opt_p = NULL;
|
||||||
char *opt_t = NULL;
|
char *opt_t = NULL;
|
||||||
char *opt_u = NULL;
|
char *opt_u = NULL;
|
||||||
|
const char *ifs;
|
||||||
int read_flags;
|
int read_flags;
|
||||||
|
sigset_t saved_blkd_set;
|
||||||
|
|
||||||
/* "!": do not abort on errors.
|
/* "!": do not abort on errors.
|
||||||
* Option string must start with "sr" to match BUILTIN_READ_xxx
|
* Option string must start with "sr" to match BUILTIN_READ_xxx
|
||||||
@ -8500,10 +8546,47 @@ static int FAST_FUNC builtin_read(char **argv)
|
|||||||
if (read_flags == (uint32_t)-1)
|
if (read_flags == (uint32_t)-1)
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
argv += optind;
|
argv += optind;
|
||||||
|
ifs = get_local_var_value("IFS"); /* can be NULL */
|
||||||
|
|
||||||
|
again:
|
||||||
|
/* We need to temporarily unblock and record signals around read */
|
||||||
|
|
||||||
|
saved_blkd_set = G.blocked_set;
|
||||||
|
{
|
||||||
|
unsigned sig;
|
||||||
|
struct sigaction sa, old_sa;
|
||||||
|
|
||||||
|
memset(&sa, 0, sizeof(sa));
|
||||||
|
sigfillset(&sa.sa_mask);
|
||||||
|
/*sa.sa_flags = 0;*/
|
||||||
|
sa.sa_handler = record_signal;
|
||||||
|
|
||||||
|
sig = 0;
|
||||||
|
do {
|
||||||
|
sig++;
|
||||||
|
if (sigismember(&G.blocked_set, sig)) {
|
||||||
|
char *sig_trap = (G.traps && G.traps[sig]) ? G.traps[sig] : NULL;
|
||||||
|
/* If has a nonempty trap... */
|
||||||
|
if ((sig_trap && sig_trap[0])
|
||||||
|
/* ...or has no trap and is SIGINT or SIGHUP */
|
||||||
|
|| (!sig_trap && (sig == SIGINT || sig == SIGHUP))
|
||||||
|
) {
|
||||||
|
sigaction(sig, &sa, &old_sa);
|
||||||
|
if (old_sa.sa_handler == SIG_IGN) /* oops... restore back to IGN! */
|
||||||
|
sigaction_set(sig, &old_sa);
|
||||||
|
else
|
||||||
|
sigdelset(&G.blocked_set, sig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (sig < NSIG-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(&saved_blkd_set, &G.blocked_set, sizeof(saved_blkd_set)) != 0)
|
||||||
|
sigprocmask_set(&G.blocked_set);
|
||||||
|
|
||||||
r = shell_builtin_read(set_local_var_from_halves,
|
r = shell_builtin_read(set_local_var_from_halves,
|
||||||
argv,
|
argv,
|
||||||
get_local_var_value("IFS"), /* can be NULL */
|
ifs,
|
||||||
read_flags,
|
read_flags,
|
||||||
opt_n,
|
opt_n,
|
||||||
opt_p,
|
opt_p,
|
||||||
@ -8511,6 +8594,17 @@ static int FAST_FUNC builtin_read(char **argv)
|
|||||||
opt_u
|
opt_u
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (memcmp(&saved_blkd_set, &G.blocked_set, sizeof(saved_blkd_set)) != 0) {
|
||||||
|
G.blocked_set = saved_blkd_set;
|
||||||
|
sigprocmask_set(&G.blocked_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((uintptr_t)r == 1 && errno == EINTR) {
|
||||||
|
unsigned sig = check_and_run_traps(0);
|
||||||
|
if (sig && sig != SIGINT)
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
|
||||||
if ((uintptr_t)r > 1) {
|
if ((uintptr_t)r > 1) {
|
||||||
bb_error_msg("%s", r);
|
bb_error_msg("%s", r);
|
||||||
r = (char*)(uintptr_t)1;
|
r = (char*)(uintptr_t)1;
|
||||||
|
@ -36,6 +36,10 @@ int FAST_FUNC is_well_formed_var_name(const char *s, char terminator)
|
|||||||
|
|
||||||
/* read builtin */
|
/* read builtin */
|
||||||
|
|
||||||
|
/* Needs to be interruptible: shell mush handle traps and shell-special signals
|
||||||
|
* while inside read. To implement this, be sure to not loop on EINTR
|
||||||
|
* and return errno == EINTR reliably.
|
||||||
|
*/
|
||||||
//TODO: use more efficient setvar() which takes a pointer to malloced "VAR=VAL"
|
//TODO: use more efficient setvar() which takes a pointer to malloced "VAR=VAL"
|
||||||
//string. hush naturally has it, and ash has setvareq().
|
//string. hush naturally has it, and ash has setvareq().
|
||||||
//Here we can simply store "VAR=" at buffer start and store read data directly
|
//Here we can simply store "VAR=" at buffer start and store read data directly
|
||||||
@ -51,6 +55,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
|
|||||||
const char *opt_u
|
const char *opt_u
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
unsigned err;
|
||||||
unsigned end_ms; /* -t TIMEOUT */
|
unsigned end_ms; /* -t TIMEOUT */
|
||||||
int fd; /* -u FD */
|
int fd; /* -u FD */
|
||||||
int nchars; /* -n NUM */
|
int nchars; /* -n NUM */
|
||||||
@ -62,6 +67,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
|
|||||||
int startword;
|
int startword;
|
||||||
smallint backslash;
|
smallint backslash;
|
||||||
|
|
||||||
|
errno = err = 0;
|
||||||
|
|
||||||
pp = argv;
|
pp = argv;
|
||||||
while (*pp) {
|
while (*pp) {
|
||||||
if (!is_well_formed_var_name(*pp, '\0')) {
|
if (!is_well_formed_var_name(*pp, '\0')) {
|
||||||
@ -153,6 +160,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
|
|||||||
do {
|
do {
|
||||||
char c;
|
char c;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
|
||||||
if (end_ms) {
|
if (end_ms) {
|
||||||
int timeout;
|
int timeout;
|
||||||
struct pollfd pfd[1];
|
struct pollfd pfd[1];
|
||||||
@ -161,8 +170,9 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
|
|||||||
pfd[0].events = POLLIN;
|
pfd[0].events = POLLIN;
|
||||||
timeout = end_ms - (unsigned)monotonic_ms();
|
timeout = end_ms - (unsigned)monotonic_ms();
|
||||||
if (timeout <= 0 /* already late? */
|
if (timeout <= 0 /* already late? */
|
||||||
|| safe_poll(pfd, 1, timeout) != 1 /* no? wait... */
|
|| poll(pfd, 1, timeout) != 1 /* no? wait... */
|
||||||
) { /* timed out! */
|
) { /* timed out! */
|
||||||
|
err = errno;
|
||||||
retval = (const char *)(uintptr_t)1;
|
retval = (const char *)(uintptr_t)1;
|
||||||
goto ret;
|
goto ret;
|
||||||
}
|
}
|
||||||
@ -170,7 +180,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
|
|||||||
|
|
||||||
if ((bufpos & 0xff) == 0)
|
if ((bufpos & 0xff) == 0)
|
||||||
buffer = xrealloc(buffer, bufpos + 0x100);
|
buffer = xrealloc(buffer, bufpos + 0x100);
|
||||||
if (nonblock_immune_read(fd, &buffer[bufpos], 1) != 1) {
|
if (nonblock_immune_read(fd, &buffer[bufpos], 1, /*loop_on_EINTR:*/ 0) != 1) {
|
||||||
|
err = errno;
|
||||||
retval = (const char *)(uintptr_t)1;
|
retval = (const char *)(uintptr_t)1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -240,6 +251,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
|
|||||||
free(buffer);
|
free(buffer);
|
||||||
if (read_flags & BUILTIN_READ_SILENT)
|
if (read_flags & BUILTIN_READ_SILENT)
|
||||||
tcsetattr(fd, TCSANOW, &old_tty);
|
tcsetattr(fd, TCSANOW, &old_tty);
|
||||||
|
|
||||||
|
errno = err;
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user