/* * * We don't free stuff here, merely disconnect the client's * network socket & resources. * * Full free of the client is done later in a safe point * where it can be guaranteed it is no longer in use */ void virNetServerClientCloseLocked(virNetServerClientPtr client) { virNetServerClientCloseFunc cf; virKeepAlivePtr ka; VIR_DEBUG("client=%p", client); if (!client->sock) return; if (client->keepalive) { virKeepAliveStop(client->keepalive); ka = client->keepalive; client->keepalive = NULL; virObjectRef(client); virObjectUnlock(client); virObjectUnref(ka); virObjectLock(client); virObjectUnref(client); } if (client->privateDataCloseFunc) { cf = client->privateDataCloseFunc; virObjectRef(client); virObjectUnlock(client); (cf)(client); virObjectLock(client); virObjectUnref(client); } /* Do now, even though we don't close the socket * until end, to ensure we don't get invoked * again due to tls shutdown */ if (client->sock) virNetSocketRemoveIOCallback(client->sock); if (client->tls) { virObjectUnref(client->tls); client->tls = NULL; } client->wantClose = true; while (client->rx) { virNetMessagePtr msg = virNetMessageQueueServe(&client->rx); virNetMessageFree(msg); } while (client->tx) { virNetMessagePtr msg = virNetMessageQueueServe(&client->tx); virNetMessageFree(msg); } if (client->sock) { virObjectUnref(client->sock); client->sock = NULL; } }
/* * Called when the stream is signalled has being able to accept * data writes. Will process all pending incoming messages * until they're all gone, or I/O blocks * * Returns 0 on success, or -1 upon fatal error */ static int daemonStreamHandleWrite(virNetServerClientPtr client, daemonClientStream *stream) { VIR_DEBUG("client=%p, stream=%p", client, stream); while (stream->rx && !stream->closed) { virNetMessagePtr msg = stream->rx; int ret; switch (msg->header.status) { case VIR_NET_OK: ret = daemonStreamHandleFinish(client, stream, msg); break; case VIR_NET_CONTINUE: ret = daemonStreamHandleWriteData(client, stream, msg); break; case VIR_NET_ERROR: default: ret = daemonStreamHandleAbort(client, stream, msg); break; } if (ret > 0) break; /* still processing data from msg */ virNetMessageQueueServe(&stream->rx); if (ret < 0) { virNetMessageFree(msg); virNetServerClientImmediateClose(client); return -1; } /* 'CONTINUE' messages don't send a reply (unless error * occurred), so to release the 'msg' object we need to * send a fake zero-length reply. Nothing actually gets * onto the wire, but this causes the client to reset * its active request count / throttling */ if (msg->header.status == VIR_NET_CONTINUE) { virNetMessageClear(msg); msg->header.type = VIR_NET_REPLY; if (virNetServerClientSendMessage(client, msg) < 0) { virNetMessageFree(msg); virNetServerClientImmediateClose(client); return -1; } } } return 0; }
int virNetClientStreamSendPacket(virNetClientStreamPtr st, virNetClientPtr client, int status, const char *data, size_t nbytes) { virNetMessagePtr msg; VIR_DEBUG("st=%p status=%d data=%p nbytes=%zu", st, status, data, nbytes); if (!(msg = virNetMessageNew(false))) return -1; virObjectLock(st); msg->header.prog = virNetClientProgramGetProgram(st->prog); msg->header.vers = virNetClientProgramGetVersion(st->prog); msg->header.status = status; msg->header.type = VIR_NET_STREAM; msg->header.serial = st->serial; msg->header.proc = st->proc; virObjectUnlock(st); if (virNetMessageEncodeHeader(msg) < 0) goto error; /* Data packets are async fire&forget, but OK/ERROR packets * need a synchronous confirmation */ if (status == VIR_NET_CONTINUE) { if (virNetMessageEncodePayloadRaw(msg, data, nbytes) < 0) goto error; if (virNetClientSendNoReply(client, msg) < 0) goto error; } else { if (virNetMessageEncodePayloadRaw(msg, NULL, 0) < 0) goto error; if (virNetClientSendWithReply(client, msg) < 0) goto error; } virNetMessageFree(msg); return nbytes; error: virNetMessageFree(msg); return -1; }
/* Check the client's access. */ static int virNetServerClientCheckAccess(virNetServerClientPtr client) { virNetMessagePtr confirm; /* Verify client certificate. */ if (virNetTLSContextCheckCertificate(client->tlsCtxt, client->tls) < 0) return -1; if (client->tx) { VIR_DEBUG("client had unexpected data pending tx after access check"); return -1; } if (!(confirm = virNetMessageNew(false))) return -1; /* Checks have succeeded. Write a '\1' byte back to the client to * indicate this (otherwise the socket is abruptly closed). * (NB. The '\1' byte is sent in an encrypted record). */ confirm->bufferLength = 1; if (VIR_ALLOC_N(confirm->buffer, confirm->bufferLength) < 0) { virReportOOMError(); virNetMessageFree(confirm); return -1; } confirm->bufferOffset = 0; confirm->buffer[0] = '\1'; client->tx = confirm; return 0; }
static void virKeepAliveTimer(int timer ATTRIBUTE_UNUSED, void *opaque) { virKeepAlivePtr ka = opaque; virNetMessagePtr msg = NULL; bool dead; void *client; virObjectLock(ka); client = ka->client; dead = virKeepAliveTimerInternal(ka, &msg); if (!dead && !msg) goto cleanup; virObjectRef(ka); virObjectUnlock(ka); if (dead) { ka->deadCB(client); } else if (ka->sendCB(client, msg) < 0) { VIR_WARN("Failed to send keepalive request to client %p", client); virNetMessageFree(msg); } virObjectLock(ka); virObjectUnref(ka); cleanup: virObjectUnlock(ka); }
static void virNetServerHandleJob(void *jobOpaque, void *opaque) { virNetServerPtr srv = opaque; virNetServerJobPtr job = jobOpaque; VIR_DEBUG("server=%p client=%p message=%p prog=%p", srv, job->client, job->msg, job->prog); if (virNetServerProcessMsg(srv, job->client, job->prog, job->msg) < 0) goto error; virNetServerLock(srv); virObjectUnref(job->prog); virNetServerUnlock(srv); virObjectUnref(job->client); VIR_FREE(job); return; error: virObjectUnref(job->prog); virNetMessageFree(job->msg); virNetServerClientClose(job->client); virObjectUnref(job->client); VIR_FREE(job); }
static void virNetServerHandleJob(void *jobOpaque, void *opaque) { virNetServerPtr srv = opaque; virNetServerJobPtr job = jobOpaque; VIR_DEBUG("server=%p client=%p message=%p prog=%p", srv, job->client, job->msg, job->prog); if (!job->prog) { /* Only send back an error for type == CALL. Other * message types are not expecting replies, so we * must just log it & drop them */ if (job->msg->header.type == VIR_NET_CALL || job->msg->header.type == VIR_NET_CALL_WITH_FDS) { if (virNetServerProgramUnknownError(job->client, job->msg, &job->msg->header) < 0) goto error; } else { VIR_INFO("Dropping client mesage, unknown program %d version %d type %d proc %d", job->msg->header.prog, job->msg->header.vers, job->msg->header.type, job->msg->header.proc); /* Send a dummy reply to free up 'msg' & unblock client rx */ virNetMessageClear(job->msg); job->msg->header.type = VIR_NET_REPLY; if (virNetServerClientSendMessage(job->client, job->msg) < 0) goto error; } goto cleanup; } if (virNetServerProgramDispatch(job->prog, srv, job->client, job->msg) < 0) goto error; virNetServerLock(srv); virNetServerProgramFree(job->prog); virNetServerUnlock(srv); cleanup: virNetServerClientFree(job->client); VIR_FREE(job); return; error: virNetServerProgramFree(job->prog); virNetMessageFree(job->msg); virNetServerClientClose(job->client); virNetServerClientFree(job->client); VIR_FREE(job); }
/* * @stream: an unused client stream * * Frees the memory associated with this inactive client * stream */ int daemonFreeClientStream(virNetServerClientPtr client, daemonClientStream *stream) { virNetMessagePtr msg; int ret = 0; if (!stream) return 0; stream->refs--; if (stream->refs) return 0; VIR_DEBUG("client=%p, proc=%d, serial=%d", client, stream->procedure, stream->serial); virObjectUnref(stream->prog); msg = stream->rx; while (msg) { virNetMessagePtr tmp = msg->next; if (client) { /* Send a dummy reply to free up 'msg' & unblock client rx */ virNetMessageClear(msg); msg->header.type = VIR_NET_REPLY; if (virNetServerClientSendMessage(client, msg) < 0) { virNetServerClientImmediateClose(client); virNetMessageFree(msg); ret = -1; } } else { virNetMessageFree(msg); } msg = tmp; } virStreamFree(stream->st); VIR_FREE(stream); return ret; }
static void virNetServerClientDispatchMessage(virNetServerClientPtr client, virNetMessagePtr msg) { virObjectLock(client); if (!client->dispatchFunc) { virNetMessageFree(msg); client->wantClose = true; virObjectUnlock(client); } else { virObjectUnlock(client); /* Accessing 'client' is safe, because virNetServerClientSetDispatcher * only permits setting 'dispatchFunc' once, so if non-NULL, it will * never change again */ client->dispatchFunc(client, msg, client->dispatchOpaque); } }
static virNetMessagePtr virKeepAliveMessage(virKeepAlivePtr ka, int proc) { virNetMessagePtr msg; const char *procstr = NULL; switch (proc) { case KEEPALIVE_PROC_PING: procstr = "request"; break; case KEEPALIVE_PROC_PONG: procstr = "response"; break; default: VIR_WARN("Refusing to send unknown keepalive message: %d", proc); return NULL; } if (!(msg = virNetMessageNew(false))) goto error; msg->header.prog = KEEPALIVE_PROGRAM; msg->header.vers = KEEPALIVE_PROTOCOL_VERSION; msg->header.type = VIR_NET_MESSAGE; msg->header.proc = proc; if (virNetMessageEncodeHeader(msg) < 0 || virNetMessageEncodePayloadEmpty(msg) < 0) { virNetMessageFree(msg); goto error; } VIR_DEBUG("Sending keepalive %s to client %p", procstr, ka->client); PROBE(RPC_KEEPALIVE_SEND, "ka=%p client=%p prog=%d vers=%d proc=%d", ka, ka->client, msg->header.prog, msg->header.vers, msg->header.proc); return msg; error: VIR_WARN("Failed to generate keepalive %s", procstr); VIR_FREE(msg); return NULL; }
/* * Callback that gets invoked when a stream becomes writable/readable */ static void daemonStreamEvent(virStreamPtr st, int events, void *opaque) { virNetServerClientPtr client = opaque; daemonClientStream *stream; daemonClientPrivatePtr priv = virNetServerClientGetPrivateData(client); virMutexLock(&priv->lock); stream = priv->streams; while (stream) { if (stream->st == st) break; stream = stream->next; } if (!stream) { VIR_WARN("event for client=%p stream st=%p, but missing stream state", client, st); virStreamEventRemoveCallback(st); goto cleanup; } VIR_DEBUG("st=%p events=%d EOF=%d closed=%d", st, events, stream->recvEOF, stream->closed); if (!stream->closed && (events & VIR_STREAM_EVENT_WRITABLE)) { if (daemonStreamHandleWrite(client, stream) < 0) { daemonRemoveClientStream(client, stream); virNetServerClientClose(client); goto cleanup; } } if (!stream->closed && !stream->recvEOF && (events & (VIR_STREAM_EVENT_READABLE))) { events = events & ~(VIR_STREAM_EVENT_READABLE); if (daemonStreamHandleRead(client, stream) < 0) { daemonRemoveClientStream(client, stream); virNetServerClientClose(client); goto cleanup; } /* If we detected EOF during read processing, * then clear hangup/error conditions, since * we want the client to see the EOF message * we just sent them */ if (stream->recvEOF) events = events & ~(VIR_STREAM_EVENT_HANGUP | VIR_STREAM_EVENT_ERROR); } /* If we have a completion/abort message, always process it */ if (stream->rx) { virNetMessagePtr msg = stream->rx; switch (msg->header.status) { case VIR_NET_CONTINUE: /* nada */ break; case VIR_NET_OK: virNetMessageQueueServe(&stream->rx); if (daemonStreamHandleFinish(client, stream, msg) < 0) { virNetMessageFree(msg); daemonRemoveClientStream(client, stream); virNetServerClientClose(client); goto cleanup; } break; case VIR_NET_ERROR: default: virNetMessageQueueServe(&stream->rx); if (daemonStreamHandleAbort(client, stream, msg) < 0) { virNetMessageFree(msg); daemonRemoveClientStream(client, stream); virNetServerClientClose(client); goto cleanup; } break; } } /* If we got HANGUP, we need to only send an empty * packet so the client sees an EOF and cleans up */ if (!stream->closed && !stream->recvEOF && (events & VIR_STREAM_EVENT_HANGUP)) { virNetMessagePtr msg; events &= ~(VIR_STREAM_EVENT_HANGUP); stream->tx = 0; stream->recvEOF = 1; if (!(msg = virNetMessageNew(false))) { daemonRemoveClientStream(client, stream); virNetServerClientClose(client); goto cleanup; } msg->cb = daemonStreamMessageFinished; msg->opaque = stream; stream->refs++; if (virNetServerProgramSendStreamData(remoteProgram, client, msg, stream->procedure, stream->serial, "", 0) < 0) { virNetMessageFree(msg); daemonRemoveClientStream(client, stream); virNetServerClientClose(client); goto cleanup; } } if (!stream->closed && (events & (VIR_STREAM_EVENT_ERROR | VIR_STREAM_EVENT_HANGUP))) { int ret; virNetMessagePtr msg; virNetMessageError rerr; memset(&rerr, 0, sizeof(rerr)); stream->closed = 1; virStreamEventRemoveCallback(stream->st); virStreamAbort(stream->st); if (events & VIR_STREAM_EVENT_HANGUP) virReportError(VIR_ERR_RPC, "%s", _("stream had unexpected termination")); else virReportError(VIR_ERR_RPC, "%s", _("stream had I/O failure")); msg = virNetMessageNew(false); if (!msg) { ret = -1; } else { ret = virNetServerProgramSendStreamError(remoteProgram, client, msg, &rerr, stream->procedure, stream->serial); } daemonRemoveClientStream(client, stream); if (ret < 0) virNetServerClientClose(client); goto cleanup; } if (stream->closed) { daemonRemoveClientStream(client, stream); } else { daemonStreamUpdateEvents(stream); } cleanup: virMutexUnlock(&priv->lock); }
/* * Process all queued client->tx messages until * we would block on I/O */ static void virNetServerClientDispatchWrite(virNetServerClientPtr client) { while (client->tx) { if (client->tx->bufferOffset < client->tx->bufferLength) { ssize_t ret; ret = virNetServerClientWrite(client); if (ret < 0) { client->wantClose = true; return; } if (ret == 0) return; /* Would block on write EAGAIN */ } if (client->tx->bufferOffset == client->tx->bufferLength) { virNetMessagePtr msg; size_t i; for (i = client->tx->donefds ; i < client->tx->nfds ; i++) { int rv; if ((rv = virNetSocketSendFD(client->sock, client->tx->fds[i])) < 0) { client->wantClose = true; return; } if (rv == 0) /* Blocking */ return; client->tx->donefds++; } #if HAVE_SASL /* Completed this 'tx' operation, so now read for all * future rx/tx to be under a SASL SSF layer */ if (client->sasl) { virNetSocketSetSASLSession(client->sock, client->sasl); virObjectUnref(client->sasl); client->sasl = NULL; } #endif /* Get finished msg from head of tx queue */ msg = virNetMessageQueueServe(&client->tx); if (msg->tracked) { client->nrequests--; /* See if the recv queue is currently throttled */ if (!client->rx && client->nrequests < client->nrequests_max) { /* Ready to recv more messages */ virNetMessageClear(msg); msg->bufferLength = VIR_NET_MESSAGE_LEN_MAX; if (VIR_ALLOC_N(msg->buffer, msg->bufferLength) < 0) { virReportOOMError(); virNetMessageFree(msg); return; } client->rx = msg; msg = NULL; client->nrequests++; } } virNetMessageFree(msg); virNetServerClientUpdateEvent(client); if (client->delayedClose) client->wantClose = true; } } }
/* * Read data until we get a complete message to process */ static void virNetServerClientDispatchRead(virNetServerClientPtr client) { readmore: if (client->rx->nfds == 0) { if (virNetServerClientRead(client) < 0) { client->wantClose = true; return; /* Error */ } } if (client->rx->bufferOffset < client->rx->bufferLength) return; /* Still not read enough */ /* Either done with length word header */ if (client->rx->bufferLength == VIR_NET_MESSAGE_LEN_MAX) { if (virNetMessageDecodeLength(client->rx) < 0) { client->wantClose = true; return; } virNetServerClientUpdateEvent(client); /* Try and read payload immediately instead of going back into poll() because chances are the data is already waiting for us */ goto readmore; } else { /* Grab the completed message */ virNetMessagePtr msg = client->rx; virNetMessagePtr response = NULL; virNetServerClientFilterPtr filter; size_t i; /* Decode the header so we can use it for routing decisions */ if (virNetMessageDecodeHeader(msg) < 0) { virNetMessageFree(msg); client->wantClose = true; return; } /* Now figure out if we need to read more data to get some * file descriptors */ if (msg->header.type == VIR_NET_CALL_WITH_FDS && virNetMessageDecodeNumFDs(msg) < 0) { virNetMessageFree(msg); client->wantClose = true; return; /* Error */ } /* Try getting the file descriptors (may fail if blocking) */ for (i = msg->donefds ; i < msg->nfds ; i++) { int rv; if ((rv = virNetSocketRecvFD(client->sock, &(msg->fds[i]))) < 0) { virNetMessageFree(msg); client->wantClose = true; return; } if (rv == 0) /* Blocking */ break; msg->donefds++; } /* Need to poll() until FDs arrive */ if (msg->donefds < msg->nfds) { /* Because DecodeHeader/NumFDs reset bufferOffset, we * put it back to what it was, so everything works * again next time we run this method */ client->rx->bufferOffset = client->rx->bufferLength; return; } /* Definitely finished reading, so remove from queue */ virNetMessageQueueServe(&client->rx); PROBE(RPC_SERVER_CLIENT_MSG_RX, "client=%p len=%zu prog=%u vers=%u proc=%u type=%u status=%u serial=%u", client, msg->bufferLength, msg->header.prog, msg->header.vers, msg->header.proc, msg->header.type, msg->header.status, msg->header.serial); if (virKeepAliveCheckMessage(client->keepalive, msg, &response)) { virNetMessageFree(msg); client->nrequests--; msg = NULL; if (response && virNetServerClientSendMessageLocked(client, response) < 0) virNetMessageFree(response); } /* Maybe send off for queue against a filter */ if (msg) { filter = client->filters; while (filter) { int ret = filter->func(client, msg, filter->opaque); if (ret < 0) { virNetMessageFree(msg); msg = NULL; if (ret < 0) client->wantClose = true; break; } if (ret > 0) { msg = NULL; break; } filter = filter->next; } } /* Send off to for normal dispatch to workers */ if (msg) { virObjectRef(client); if (!client->dispatchFunc || client->dispatchFunc(client, msg, client->dispatchOpaque) < 0) { virNetMessageFree(msg); client->wantClose = true; virObjectUnref(client); return; } } /* Possibly need to create another receive buffer */ if (client->nrequests < client->nrequests_max) { if (!(client->rx = virNetMessageNew(true))) { client->wantClose = true; } else { client->rx->bufferLength = VIR_NET_MESSAGE_LEN_MAX; if (VIR_ALLOC_N(client->rx->buffer, client->rx->bufferLength) < 0) { virReportOOMError(); client->wantClose = true; } else { client->nrequests++; } } } virNetServerClientUpdateEvent(client); } }
int virNetClientStreamRecvPacket(virNetClientStreamPtr st, virNetClientPtr client, char *data, size_t nbytes, bool nonblock) { int rv = -1; VIR_DEBUG("st=%p client=%p data=%p nbytes=%zu nonblock=%d", st, client, data, nbytes, nonblock); virObjectLock(st); if (!st->incomingOffset && !st->incomingEOF) { virNetMessagePtr msg; int ret; if (nonblock) { VIR_DEBUG("Non-blocking mode and no data available"); rv = -2; goto cleanup; } if (!(msg = virNetMessageNew(false))) goto cleanup; msg->header.prog = virNetClientProgramGetProgram(st->prog); msg->header.vers = virNetClientProgramGetVersion(st->prog); msg->header.type = VIR_NET_STREAM; msg->header.serial = st->serial; msg->header.proc = st->proc; msg->header.status = VIR_NET_CONTINUE; VIR_DEBUG("Dummy packet to wait for stream data"); virObjectUnlock(st); ret = virNetClientSendWithReplyStream(client, msg, st); virObjectLock(st); virNetMessageFree(msg); if (ret < 0) goto cleanup; } VIR_DEBUG("After IO %zu", st->incomingOffset); if (st->incomingOffset) { int want = st->incomingOffset; if (want > nbytes) want = nbytes; memcpy(data, st->incoming, want); if (want < st->incomingOffset) { memmove(st->incoming, st->incoming + want, st->incomingOffset - want); st->incomingOffset -= want; } else { VIR_FREE(st->incoming); st->incomingOffset = st->incomingLength = 0; } rv = want; } else { rv = 0; } virNetClientStreamEventTimerUpdate(st); cleanup: virObjectUnlock(st); return rv; }
int virNetClientProgramCall(virNetClientProgramPtr prog, virNetClientPtr client, unsigned serial, int proc, size_t noutfds, int *outfds, size_t *ninfds, int **infds, xdrproc_t args_filter, void *args, xdrproc_t ret_filter, void *ret) { virNetMessagePtr msg; size_t i; if (infds) *infds = NULL; if (ninfds) *ninfds = 0; if (!(msg = virNetMessageNew(false))) return -1; msg->header.prog = prog->program; msg->header.vers = prog->version; msg->header.status = VIR_NET_OK; msg->header.type = noutfds ? VIR_NET_CALL_WITH_FDS : VIR_NET_CALL; msg->header.serial = serial; msg->header.proc = proc; msg->nfds = noutfds; if (VIR_ALLOC_N(msg->fds, msg->nfds) < 0) { virReportOOMError(); goto error; } for (i = 0; i < msg->nfds; i++) msg->fds[i] = -1; for (i = 0; i < msg->nfds; i++) { if ((msg->fds[i] = dup(outfds[i])) < 0) { virReportSystemError(errno, _("Cannot duplicate FD %d"), outfds[i]); goto error; } if (virSetInherit(msg->fds[i], false) < 0) { virReportSystemError(errno, _("Cannot set close-on-exec %d"), msg->fds[i]); goto error; } } if (virNetMessageEncodeHeader(msg) < 0) goto error; if (msg->nfds && virNetMessageEncodeNumFDs(msg) < 0) goto error; if (virNetMessageEncodePayload(msg, args_filter, args) < 0) goto error; if (virNetClientSendWithReply(client, msg) < 0) goto error; /* None of these 3 should ever happen here, because * virNetClientSend should have validated the reply, * but it doesn't hurt to check again. */ if (msg->header.type != VIR_NET_REPLY && msg->header.type != VIR_NET_REPLY_WITH_FDS) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unexpected message type %d"), msg->header.type); goto error; } if (msg->header.proc != proc) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unexpected message proc %d != %d"), msg->header.proc, proc); goto error; } if (msg->header.serial != serial) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unexpected message serial %d != %d"), msg->header.serial, serial); goto error; } switch (msg->header.status) { case VIR_NET_OK: if (infds && ninfds) { *ninfds = msg->nfds; if (VIR_ALLOC_N(*infds, *ninfds) < 0) { virReportOOMError(); goto error; } for (i = 0; i < *ninfds; i++) (*infds)[i] = -1; for (i = 0; i < *ninfds; i++) { if (((*infds)[i] = dup(msg->fds[i])) < 0) { virReportSystemError(errno, _("Cannot duplicate FD %d"), msg->fds[i]); goto error; } if (virSetInherit((*infds)[i], false) < 0) { virReportSystemError(errno, _("Cannot set close-on-exec %d"), (*infds)[i]); goto error; } } } if (virNetMessageDecodePayload(msg, ret_filter, ret) < 0) goto error; break; case VIR_NET_ERROR: virNetClientProgramDispatchError(prog, msg); goto error; default: virReportError(VIR_ERR_RPC, _("Unexpected message status %d"), msg->header.status); goto error; } virNetMessageFree(msg); return 0; error: virNetMessageFree(msg); if (infds && ninfds) { for (i = 0; i < *ninfds; i++) VIR_FORCE_CLOSE((*infds)[i]); } return -1; }