[libvirt] [PATCH 1/2] Add virFork() function to utils.
Daniel P. Berrange
berrange at redhat.com
Thu Feb 18 12:50:38 UTC 2010
On Wed, Feb 17, 2010 at 02:29:27PM -0500, Laine Stump wrote:
> virFork() contains bookkeeping that must be done any time a process
> forks. Currently this includes:
>
> 1) Call virLogLock() prior to fork() and virLogUnlock() just after,
> to avoid a deadlock if some other thread happens to hold that lock
> during the fork.
>
> 2) Reset the logging hooks and send all child process log messages to
> stderr.
>
> 3) Block all signals prior to fork(), then either a) reset the signal
> mask for the parent process, or b) clear the signal mask for the
> child process.
>
> util.c, util.h: add virFork() function, based on what is currently
> done in __virExec().
> ---
> src/util/util.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
> src/util/util.h | 1 +
> 2 files changed, 116 insertions(+), 0 deletions(-)
>
> diff --git a/src/util/util.c b/src/util/util.c
> index cdab300..f508f6b 100644
> --- a/src/util/util.c
> +++ b/src/util/util.c
> @@ -296,6 +296,121 @@ static int virClearCapabilities(void)
> }
> #endif
>
> +
> +/* virFork() - fork a new process while avoiding various race/deadlock conditions
> +
> + @pid - a pointer to a pid_t that will receive the return value from
> + fork()
> +
> + on return from virFork(), if *pid < 0, the fork failed and there is
> + no new process. Otherwise, just like fork(), if *pid == 0, it is the
> + child process returning, and if *pid > 0, it is the parent.
> +
> + Even if *pid >= 0, if the return value from virFork() is < 0, it
> + indicates a failure that occurred in the parent or child process
> + after the fork. In this case, the child process should call
> + _exit(1) after doing any additional error reporting.
> +
> + */
> +int virFork(pid_t *pid) {
> + sigset_t oldmask, newmask;
> + struct sigaction sig_action;
> + int saved_errno, ret = -1;
> +
> + *pid = -1;
> +
> + /*
> + * 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) {
> + saved_errno = errno;
> + virReportSystemError(errno,
> + "%s", _("cannot block signals"));
> + goto cleanup;
> + }
> +
> + /* Ensure we hold the logging lock, to protect child processes
> + * from deadlocking on another thread's inherited mutex state */
> + virLogLock();
> +
> + *pid = fork();
> + saved_errno = errno; /* save for caller */
> +
> + /* Unlock for both parent and child process */
> + virLogUnlock();
> +
> + if (*pid < 0) {
> + virReportSystemError(saved_errno,
> + "%s", _("cannot fork child process"));
> + goto cleanup;
> + }
Tiny bug here, in that we forget to unblock the parent's signals in
this error path.
> +
> + if (*pid) {
> +
> + /* parent process */
> +
> + /* Restore our original signal mask now that the child is
> + safely running */
> + if (pthread_sigmask(SIG_SETMASK, &oldmask, NULL) != 0) {
> + saved_errno = errno; /* save for caller */
> + virReportSystemError(errno, "%s", _("cannot unblock signals"));
> + goto cleanup;
> + }
> + ret = 0;
> +
> + } else {
> +
> + /* child process */
> +
> + int logprio;
> + int i;
> +
> + /* Remove any error callback so errors in child now
> + get sent to stderr where they stand a fighting chance
> + of being seen / logged */
> + virSetErrorFunc(NULL, NULL);
> +
> + /* Make sure any hook logging is sent to stderr, since child
> + * process may close the logfile FDs */
> + logprio = virLogGetDefaultPriority();
> + virLogReset();
> + virLogSetDefaultPriority(logprio);
> +
> + /* Clear out all signal handlers from parent so nothing
> + unexpected can happen in our child once we unblock
> + signals */
> + sig_action.sa_handler = SIG_DFL;
> + sig_action.sa_flags = 0;
> + sigemptyset(&sig_action.sa_mask);
> +
> + for (i = 1; i < NSIG; i++) {
> + /* 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
> + and don't want to propagate that to children */
> + sigemptyset(&newmask);
> + if (pthread_sigmask(SIG_SETMASK, &newmask, NULL) != 0) {
> + saved_errno = errno; /* save for caller */
> + virReportSystemError(errno, "%s", _("cannot unblock signals"));
> + goto cleanup;
> + }
> + ret = 0;
> + }
> +
> +cleanup:
> + if (ret < 0)
> + errno = saved_errno;
> + return ret;
> +}
> +
> /*
> * @argv argv to exec
> * @envp optional environment to use for exec
> diff --git a/src/util/util.h b/src/util/util.h
> index 4207508..8460100 100644
> --- a/src/util/util.h
> +++ b/src/util/util.h
> @@ -81,6 +81,7 @@ int virRun(const char *const*argv, int *status) ATTRIBUTE_RETURN_CHECK;
> int virRunWithHook(const char *const*argv,
> virExecHook hook, void *data,
> int *status) ATTRIBUTE_RETURN_CHECK;
> +int virFork(pid_t *pid);
>
> int virFileReadLimFD(int fd, int maxlen, char **buf) ATTRIBUTE_RETURN_CHECK;
>
Daniel
--
|: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :|
|: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :|
|: http://autobuild.org -o- http://search.cpan.org/~danberr/ :|
|: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|
More information about the libvir-list
mailing list