Allow the parent process to perform a bi-directional handshake
with the child process during fork/exec. The child process
will fork and do its initial setup. Immediately prior to the
exec(), it will stop & wait for a handshake from the parent
process. The parent process will spawn the child and wait
until the child reaches the handshake point. It will do
whatever extra setup work is required, before signalling the
child to continue.
The implementation of this is done using two pairs of blocking
pipes. The first pair is used to block the parent, until the
child writes a single byte. Then the second pair pair is used
to block the child, until the parent confirms with another
single byte.
* src/util/command.c, src/util/command.h,
src/libvirt_private.syms: Add APIs to perform a handshake
---
src/libvirt_private.syms | 3 +
src/util/command.c | 141 +++++++++++++++++++++++++++++++++++++++++++++-
src/util/command.h | 5 ++
3 files changed, 147 insertions(+), 2 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index cb0214e..99df0f7 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -98,11 +98,14 @@ virCommandAddEnvString;
virCommandClearCaps;
virCommandDaemonize;
virCommandFree;
+virCommandHandshakeNotify;
+virCommandHandshakeWait;
virCommandNew;
virCommandNewArgList;
virCommandNewArgs;
virCommandNonblockingFDs;
virCommandPreserveFD;
+virCommandRequireHandshake;
virCommandRun;
virCommandRunAsync;
virCommandSetErrorBuffer;
diff --git a/src/util/command.c b/src/util/command.c
index abd2dc4..7a5d333 100644
--- a/src/util/command.c
+++ b/src/util/command.c
@@ -36,6 +36,11 @@
#include "files.h"
#include "buf.h"
+#include <stdlib.h>
+#include <stdbool.h>
+#include <poll.h>
+#include <sys/wait.h>
+
#define VIR_FROM_THIS VIR_FROM_NONE
#define virCommandError(code, ...) \
@@ -72,6 +77,10 @@ struct _virCommand {
int *outfdptr;
int *errfdptr;
+ bool handshake;
+ int handshakeWait[2];
+ int handshakeNotify[2];
+
virExecHook hook;
void *opaque;
@@ -102,6 +111,11 @@ virCommandNewArgs(const char *const*args)
if (VIR_ALLOC(cmd) < 0)
return NULL;
+ cmd->handshakeWait[0] = -1;
+ cmd->handshakeWait[1] = -1;
+ cmd->handshakeNotify[0] = -1;
+ cmd->handshakeNotify[1] = -1;
+
FD_ZERO(&cmd->preserve);
FD_ZERO(&cmd->transfer);
cmd->infd = cmd->outfd = cmd->errfd = -1;
@@ -1078,7 +1092,6 @@ virCommandRun(virCommandPtr cmd, int *exitstatus)
return ret;
}
-
/*
* Perform all virCommand-specific actions, along with the user hook.
*/
@@ -1088,12 +1101,61 @@ virCommandHook(void *data)
virCommandPtr cmd = data;
int res = 0;
- if (cmd->hook)
+ if (cmd->hook) {
+ VIR_DEBUG("Run hook %p %p", cmd->hook, cmd->opaque);
res = cmd->hook(cmd->opaque);
+ VIR_DEBUG("Done hook %d", res);
+ }
if (res == 0 && cmd->pwd) {
VIR_DEBUG("Running child in %s", cmd->pwd);
res = chdir(cmd->pwd);
+ if (res < 0) {
+ virReportSystemError(errno,
+ _("Unable to change to %s"), cmd->pwd);
+ }
+ }
+ if (cmd->handshake) {
+ char c = res < 0 ? '0' : '1';
+ int rv;
+ VIR_DEBUG("Notifying parent for handshake start on %d",
cmd->handshakeWait[1]);
+ if (safewrite(cmd->handshakeWait[1], &c, sizeof(c)) != sizeof(c)) {
+ virReportSystemError(errno, "%s", _("Unable to notify parent
process"));
+ return -1;
+ }
+
+ /* On failure we pass the error message back to parent,
+ * so they don't have to dig through stderr logs
+ */
+ if (res < 0) {
+ virErrorPtr err = virGetLastError();
+ const char *msg = err ? err->message :
+ _("Unknown failure during hook execution");
+ size_t len = strlen(msg) + 1;
+ if (safewrite(cmd->handshakeWait[1], msg, len) != len) {
+ virReportSystemError(errno, "%s", _("Unable to send error
to parent process"));
+ return -1;
+ }
+ return -1;
+ }
+
+ VIR_DEBUG("Waiting on parent for handshake complete on %d",
cmd->handshakeNotify[0]);
+ if ((rv = saferead(cmd->handshakeNotify[0], &c, sizeof(c))) != sizeof(c))
{
+ if (rv < 0)
+ virReportSystemError(errno, "%s", _("Unable to wait on
parent process"));
+ else
+ virReportSystemError(EIO, "%s", _("libvirtd quit during
handshake"));
+ return -1;
+ }
+ if (c != '1') {
+ virReportSystemError(EINVAL, _("Unexpected confirm code '%c'
from parent process"), c);
+ return -1;
+ }
+ VIR_FORCE_CLOSE(cmd->handshakeWait[1]);
+ VIR_FORCE_CLOSE(cmd->handshakeNotify[0]);
}
+
+ VIR_DEBUG("Hook is done %d", res);
+
return res;
}
@@ -1170,6 +1232,10 @@ virCommandRunAsync(virCommandPtr cmd, pid_t *pid)
FD_CLR(i, &cmd->transfer);
}
}
+ if (cmd->handshake) {
+ VIR_FORCE_CLOSE(cmd->handshakeWait[1]);
+ VIR_FORCE_CLOSE(cmd->handshakeNotify[0]);
+ }
if (ret == 0 && pid)
*pid = cmd->pid;
@@ -1234,6 +1300,70 @@ virCommandWait(virCommandPtr cmd, int *exitstatus)
}
+void virCommandRequireHandshake(virCommandPtr cmd)
+{
+ if (pipe(cmd->handshakeWait) < 0) {
+ cmd->has_error = errno;
+ return;
+ }
+ if (pipe(cmd->handshakeNotify) < 0) {
+ VIR_FORCE_CLOSE(cmd->handshakeWait[0]);
+ VIR_FORCE_CLOSE(cmd->handshakeWait[1]);
+ cmd->has_error = errno;
+ return;
+ }
+
+ VIR_DEBUG("Transfer handshake wait=%d notify=%d",
+ cmd->handshakeWait[1], cmd->handshakeNotify[0]);
+ virCommandPreserveFD(cmd, cmd->handshakeWait[1]);
+ virCommandPreserveFD(cmd, cmd->handshakeNotify[0]);
+ cmd->handshake = true;
+}
+
+int virCommandHandshakeWait(virCommandPtr cmd)
+{
+ char c;
+ int rv;
+ VIR_DEBUG("Wait for handshake on %d", cmd->handshakeWait[0]);
+ if ((rv = saferead(cmd->handshakeWait[0], &c, sizeof(c))) != sizeof(c)) {
+ if (rv < 0)
+ virReportSystemError(errno, "%s", _("Unable to wait for child
process"));
+ else
+ virReportSystemError(EIO, "%s", _("Child process quit during
startup handshake"));
+ return -1;
+ }
+ if (c != '1') {
+ char *msg;
+ ssize_t len;
+ if (VIR_ALLOC_N(msg, 1024) < 0) {
+ virReportOOMError();
+ return -1;
+ }
+ if ((len = saferead(cmd->handshakeWait[0], msg, 1024)) < 0) {
+ VIR_FREE(msg);
+ virReportSystemError(errno, "%s", _("No error message from
child failure"));
+ return -1;
+ }
+ msg[len-1] = '\0';
+ virCommandError(VIR_ERR_INTERNAL_ERROR, "%s", msg);
+ VIR_FREE(msg);
+ return -1;
+ }
+ return 0;
+}
+
+int virCommandHandshakeNotify(virCommandPtr cmd)
+{
+ char c = '1';
+ VIR_DEBUG("Notify handshake on %d", cmd->handshakeWait[0]);
+ if (safewrite(cmd->handshakeNotify[1], &c, sizeof(c)) != sizeof(c)) {
+ virReportSystemError(errno, "%s", _("Unable to notify child
process"));
+ return -1;
+ }
+ return 0;
+}
+
+
/*
* Release all resources
*/
@@ -1265,6 +1395,13 @@ virCommandFree(virCommandPtr cmd)
VIR_FREE(cmd->pwd);
+ if (cmd->handshake) {
+ VIR_FORCE_CLOSE(cmd->handshakeWait[0]);
+ VIR_FORCE_CLOSE(cmd->handshakeWait[1]);
+ VIR_FORCE_CLOSE(cmd->handshakeNotify[0]);
+ VIR_FORCE_CLOSE(cmd->handshakeNotify[1]);
+ }
+
VIR_FREE(cmd->pidfile);
VIR_FREE(cmd);
diff --git a/src/util/command.h b/src/util/command.h
index 59d0ee3..c5b0a64 100644
--- a/src/util/command.h
+++ b/src/util/command.h
@@ -268,6 +268,11 @@ int virCommandRunAsync(virCommandPtr cmd,
int virCommandWait(virCommandPtr cmd,
int *exitstatus) ATTRIBUTE_RETURN_CHECK;
+void virCommandRequireHandshake(virCommandPtr cmd);
+
+int virCommandHandshakeWait(virCommandPtr cmd);
+int virCommandHandshakeNotify(virCommandPtr cmd);
+
/*
* Release all resources
*/
--
1.7.3.4