virprocess: Introduce virProcessRunInFork

This new helper can be used to spawn a child process and run
passed callback from it. This will come handy esp. if the
callback is not thread safe.

Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: John Ferlan <jferlan@redhat.com>
This commit is contained in:
Michal Privoznik 2018-10-16 10:50:33 +02:00
parent 24b74d187c
commit 9e8e74f463
3 changed files with 103 additions and 0 deletions

View File

@ -2633,6 +2633,7 @@ virProcessKill;
virProcessKillPainfully;
virProcessKillPainfullyDelay;
virProcessNamespaceAvailable;
virProcessRunInFork;
virProcessRunInMountNamespace;
virProcessSchedPolicyTypeFromString;
virProcessSchedPolicyTypeToString;

View File

@ -1165,6 +1165,92 @@ virProcessRunInMountNamespace(pid_t pid,
}
static int
virProcessRunInForkHelper(int errfd,
pid_t ppid,
virProcessForkCallback cb,
void *opaque)
{
if (cb(ppid, opaque) < 0) {
virErrorPtr err = virGetLastError();
if (err) {
size_t len = strlen(err->message) + 1;
ignore_value(safewrite(errfd, err->message, len));
}
return -1;
}
return 0;
}
/**
* virProcessRunInFork:
* @cb: callback to run
* @opaque: opaque data to @cb
*
* Do the fork and run @cb in the child. This can be used when
* @cb does something thread unsafe, for instance. All signals
* will be reset to have their platform default handlers and
* unmasked. @cb must only use async signal safe functions. In
* particular no mutexes should be used in @cb, unless steps were
* taken before forking to guarantee a predictable state. @cb
* must not exec any external binaries, instead
* virCommand/virExec should be used for that purpose.
*
* On return, the returned value is either -1 with error message
* reported if something went bad in the parent, if child has
* died from a signal or if the child returned EXIT_CANCELED.
* Otherwise the returned value is the exit status of the child.
*/
int
virProcessRunInFork(virProcessForkCallback cb,
void *opaque)
{
int ret = -1;
pid_t child = -1;
pid_t parent = getpid();
int errfd[2] = { -1, -1 };
if (pipe2(errfd, O_CLOEXEC) < 0) {
virReportSystemError(errno, "%s",
_("Cannot create pipe for child"));
return -1;
}
if ((child = virFork()) < 0)
goto cleanup;
if (child == 0) {
VIR_FORCE_CLOSE(errfd[0]);
ret = virProcessRunInForkHelper(errfd[1], parent, cb, opaque);
VIR_FORCE_CLOSE(errfd[1]);
_exit(ret < 0 ? EXIT_CANCELED : ret);
} else {
int status;
VIR_AUTOFREE(char *) buf = NULL;
VIR_FORCE_CLOSE(errfd[1]);
ignore_value(virFileReadHeaderFD(errfd[0], 1024, &buf));
ret = virProcessWait(child, &status, false);
if (ret == 0) {
ret = status == EXIT_CANCELED ? -1 : status;
if (ret) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("child reported (status=%d): %s"),
status, NULLSTR(buf));
}
}
}
cleanup:
VIR_FORCE_CLOSE(errfd[0]);
VIR_FORCE_CLOSE(errfd[1]);
return ret;
}
#if defined(HAVE_SYS_MOUNT_H) && defined(HAVE_UNSHARE)
int
virProcessSetupPrivateMountNS(void)

View File

@ -93,6 +93,22 @@ int virProcessRunInMountNamespace(pid_t pid,
virProcessNamespaceCallback cb,
void *opaque);
/**
* virProcessForkCallback:
* @ppid: parent's pid
* @opaque: opaque data
*
* Callback to run in fork()-ed process.
*
* Returns: 0 on success,
* -1 on error (treated as EXIT_CANCELED)
*/
typedef int (*virProcessForkCallback)(pid_t ppid,
void *opaque);
int virProcessRunInFork(virProcessForkCallback cb,
void *opaque);
int virProcessSetupPrivateMountNS(void);
int virProcessSetScheduler(pid_t pid,