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:
parent
8f8a90522c
commit
2dcc53599c
@ -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
|
||||
|
@ -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");
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user