wordexp(3): fix some bugs with signals and long outputs

* retry various system calls on EINTR
* retry the rest after a short read (common if there is more than about 1K
  of output)
* block SIGCHLD like system(3) does (note that this does not and cannot
  work fully in threaded programs, they will need to be careful with wait
  functions)

PR:		90580
MFC after:	1 month
This commit is contained in:
jilles 2009-10-23 14:50:11 +00:00
parent 8f8a90522c
commit 2dcc53599c
2 changed files with 99 additions and 19 deletions

View File

@ -28,8 +28,10 @@
#include <sys/cdefs.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -73,6 +75,24 @@ wordexp(const char * __restrict words, wordexp_t * __restrict we, int flags)
return (0);
}
static size_t
we_read_fully(int fd, char *buffer, size_t len)
{
size_t done;
ssize_t nread;
done = 0;
do {
nread = _read(fd, buffer + done, len - done);
if (nread == -1 && errno == EINTR)
continue;
if (nread <= 0)
break;
done += nread;
} while (done != len);
return done;
}
/*
* we_askshell --
* Use the `wordexp' /bin/sh builtin function to do most of the work
@ -90,20 +110,31 @@ we_askshell(const char *words, wordexp_t *we, int flags)
size_t sofs; /* Offset into we->we_strings */
size_t vofs; /* Offset into we->we_wordv */
pid_t pid; /* Process ID of child */
pid_t wpid; /* waitpid return value */
int status; /* Child exit status */
int error; /* Our return value */
int serrno; /* errno to return */
char *ifs; /* IFS env. var. */
char *np, *p; /* Handy pointers */
char *nstrings; /* Temporary for realloc() */
char **nwv; /* Temporary for realloc() */
sigset_t newsigblock, oldsigblock;
serrno = errno;
if ((ifs = getenv("IFS")) == NULL)
ifs = " \t\n";
if (pipe(pdes) < 0)
return (WRDE_NOSPACE); /* XXX */
(void)sigemptyset(&newsigblock);
(void)sigaddset(&newsigblock, SIGCHLD);
(void)_sigprocmask(SIG_BLOCK, &newsigblock, &oldsigblock);
if ((pid = fork()) < 0) {
serrno = errno;
_close(pdes[0]);
_close(pdes[1]);
(void)_sigprocmask(SIG_SETMASK, &oldsigblock, NULL);
errno = serrno;
return (WRDE_NOSPACE); /* XXX */
}
else if (pid == 0) {
@ -114,6 +145,7 @@ we_askshell(const char *words, wordexp_t *we, int flags)
int devnull;
char *cmd;
(void)_sigprocmask(SIG_SETMASK, &oldsigblock, NULL);
_close(pdes[0]);
if (_dup2(pdes[1], STDOUT_FILENO) < 0)
_exit(1);
@ -139,10 +171,11 @@ we_askshell(const char *words, wordexp_t *we, int flags)
* the expanded words separated by nulls.
*/
_close(pdes[1]);
if (_read(pdes[0], wbuf, 8) != 8 || _read(pdes[0], bbuf, 8) != 8) {
_close(pdes[0]);
_waitpid(pid, &status, 0);
return (flags & WRDE_UNDEF ? WRDE_BADVAL : WRDE_SYNTAX);
if (we_read_fully(pdes[0], wbuf, 8) != 8 ||
we_read_fully(pdes[0], bbuf, 8) != 8) {
error = flags & WRDE_UNDEF ? WRDE_BADVAL : WRDE_SYNTAX;
serrno = errno;
goto cleanup;
}
wbuf[8] = bbuf[8] = '\0';
nwords = strtol(wbuf, NULL, 16);
@ -162,33 +195,38 @@ we_askshell(const char *words, wordexp_t *we, int flags)
if ((nwv = realloc(we->we_wordv, (we->we_wordc + 1 +
(flags & WRDE_DOOFFS ? we->we_offs : 0)) *
sizeof(char *))) == NULL) {
_close(pdes[0]);
_waitpid(pid, &status, 0);
return (WRDE_NOSPACE);
error = WRDE_NOSPACE;
goto cleanup;
}
we->we_wordv = nwv;
if ((nstrings = realloc(we->we_strings, we->we_nbytes)) == NULL) {
_close(pdes[0]);
_waitpid(pid, &status, 0);
return (WRDE_NOSPACE);
error = WRDE_NOSPACE;
goto cleanup;
}
for (i = 0; i < vofs; i++)
if (we->we_wordv[i] != NULL)
we->we_wordv[i] += nstrings - we->we_strings;
we->we_strings = nstrings;
if (_read(pdes[0], we->we_strings + sofs, nbytes) != nbytes) {
_close(pdes[0]);
_waitpid(pid, &status, 0);
return (flags & WRDE_UNDEF ? WRDE_BADVAL : WRDE_SYNTAX);
if (we_read_fully(pdes[0], we->we_strings + sofs, nbytes) != nbytes) {
error = flags & WRDE_UNDEF ? WRDE_BADVAL : WRDE_SYNTAX;
serrno = errno;
goto cleanup;
}
if (_waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) ||
WEXITSTATUS(status) != 0) {
_close(pdes[0]);
return (flags & WRDE_UNDEF ? WRDE_BADVAL : WRDE_SYNTAX);
}
error = 0;
cleanup:
_close(pdes[0]);
do
wpid = _waitpid(pid, &status, 0);
while (wpid < 0 && errno == EINTR);
(void)_sigprocmask(SIG_SETMASK, &oldsigblock, NULL);
if (error != 0) {
errno = serrno;
return (error);
}
if (wpid < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
return (flags & WRDE_UNDEF ? WRDE_BADVAL : WRDE_SYNTAX);
/*
* Break the null-terminated expanded word strings out into

View File

@ -32,17 +32,36 @@
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wordexp.h>
static void
chld_handler(int x)
{
int status, serrno;
(void)x;
serrno = errno;
while (waitpid(-1, &status, WNOHANG) > 0)
;
errno = serrno;
}
int
main(int argc, char *argv[])
{
struct sigaction sa;
wordexp_t we;
int r;
int i;
char longdata[6 * 10000 + 1];
/* Test that the macros are there. */
(void)(WRDE_APPEND + WRDE_DOOFFS + WRDE_NOCMD + WRDE_REUSE +
@ -59,6 +78,15 @@ main(int argc, char *argv[])
assert(we.we_wordv[2] == NULL);
wordfree(&we);
/* Long output. */
for (i = 0; i < 10000; i++)
snprintf(longdata + 6 * i, 7, "%05d ", i);
r = wordexp(longdata, &we, 0);
assert(r == 0);
assert(we.we_wordc == 10000);
assert(we.we_wordv[10000] == NULL);
wordfree(&we);
/* WRDE_DOOFFS */
we.we_offs = 3;
r = wordexp("hello world", &we, WRDE_DOOFFS);
@ -167,6 +195,20 @@ main(int argc, char *argv[])
r = wordexp("test } test", &we, 0);
assert(r == WRDE_BADCHAR);
/* With a SIGCHLD handler that reaps all zombies. */
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sa.sa_handler = chld_handler;
r = sigaction(SIGCHLD, &sa, NULL);
assert(r == 0);
r = wordexp("hello world", &we, 0);
assert(r == 0);
assert(we.we_wordc == 2);
assert(strcmp(we.we_wordv[0], "hello") == 0);
assert(strcmp(we.we_wordv[1], "world") == 0);
assert(we.we_wordv[2] == NULL);
wordfree(&we);
printf("PASS wordexp()\n");
printf("PASS wordfree()\n");