[libvirt] PATCH: Block & reset signals when fork/exec'ing children
Jim Meyering
jim at meyering.net
Tue Aug 12 18:32:22 UTC 2008
"Daniel P. Berrange" <berrange at redhat.com> wrote:
> The LXC patches identified a race condition between fork/exec'ing child
> processes and signal handlers.
Looks fine modulo a few details:
> diff -r 1dbfb08d365d src/util.c
...
> @@ -104,9 +109,23 @@
> _virExec(virConnectPtr conn,
> const char *const*argv,
> int *retpid, int infd, int *outfd, int *errfd, int non_block) {
> - int pid, null;
> + int pid, null, i;
Use pid_t as the type for "pid".
Hmm... it'd be good to do the same to preexisting *retpid, (above) too.
> int pipeout[2] = {-1,-1};
> int pipeerr[2] = {-1,-1};
> + sigset_t oldmask, newmask;
> + struct sigaction sig_action;
> +
> + /*
> + * Need to block signals now, so that child process can safely
> + * kill off caller's signal handlers without a race.
> + */
> + sigfillset(&newmask);
> + if (pthread_sigmask(SIG_SETMASK, &newmask, &oldmask) < 0) {
This should test "!= 0", since the function is specified to return
"nonzero" upon failure. Two more uses below.
> + ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> + _("cannot block signals: %s"),
> + strerror(errno));
> + return -1;
> + }
>
> if ((null = open(_PATH_DEVNULL, O_RDONLY)) < 0) {
> ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> @@ -122,6 +141,34 @@
> goto cleanup;
> }
>
> + if (outfd) {
> + if(non_block &&
I know this is moved code, but you might want to take the opportunity
to add a space between the "if" and the following open parenthesis.
> + virSetNonBlock(pipeout[0]) == -1) {
> + ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> + _("Failed to set non-blocking file descriptor flag"));
> + goto cleanup;
> + }
> +
> + if(virSetCloseExec(pipeout[0]) == -1) {
> + ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> + _("Failed to set close-on-exec file descriptor flag"));
> + goto cleanup;
> + }
> + }
> + if (errfd) {
> + if(non_block &&
> + virSetNonBlock(pipeerr[0]) == -1) {
> + ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> + _("Failed to set non-blocking file descriptor flag"));
> + goto cleanup;
> + }
> + if(virSetCloseExec(pipeerr[0]) == -1) {
> + ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> + _("Failed to set close-on-exec file descriptor flag"));
> + goto cleanup;
> + }
> + }
> +
> if ((pid = fork()) < 0) {
> ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> _("cannot fork child process: %s"), strerror(errno));
> @@ -132,33 +179,48 @@
> close(null);
> if (outfd) {
> close(pipeout[1]);
> - if(non_block)
> - if(virSetNonBlock(pipeout[0]) == -1)
> - ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> - _("Failed to set non-blocking file descriptor flag"));
> -
> - if(virSetCloseExec(pipeout[0]) == -1)
> - ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> - _("Failed to set close-on-exec file descriptor flag"));
> *outfd = pipeout[0];
> }
> if (errfd) {
> close(pipeerr[1]);
> - if(non_block)
> - if(virSetNonBlock(pipeerr[0]) == -1)
> - ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> - _("Failed to set non-blocking file descriptor flag"));
> -
> - if(virSetCloseExec(pipeerr[0]) == -1)
> - ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> - _("Failed to set close-on-exec file descriptor flag"));
> *errfd = pipeerr[0];
> }
> *retpid = pid;
> +
> + /* Restore our original signal mask now child is safely
> + running */
> + if (pthread_sigmask(SIG_SETMASK, &oldmask, NULL) < 0) {
> + ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> + _("cannot unblock signals: %s"),
> + strerror(errno));
> + return -1;
> + }
> +
> return 0;
> }
>
> /* child */
> +
> + /* Clear out all signal handlers from parent so nothing
> + unexpected can happen in our child here */
> + sig_action.sa_handler = SIG_DFL;
> + sig_action.sa_flags = 0;
> + sigemptyset(&sig_action.sa_mask);
> +
> + for (i = 0 ; i < NSIG ; i++)
I'd start at 1, since afaik, sigaction(0, ... serves no purpose.
> + /* Only possible errors are EFAULT or EINVAL
> + The former wont happen, the latter we
> + expect, so no need to check return value */
> + sigaction(i, &sig_action, NULL);
> +
> + /* Unmask all signals in child, since we've no idea
> + what the caller's done with their signal mask */
> + if (pthread_sigmask(SIG_SETMASK, &oldmask, NULL) < 0) {
> + ReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
> + _("cannot unblock signals: %s"),
> + strerror(errno));
> + return -1;
> + }
>
> if (pipeout[0] > 0 && close(pipeout[0]) < 0)
> _exit(1);
More information about the libvir-list
mailing list