/* * virnetclientstream.c: generic network RPC client stream * * Copyright (C) 2006-2011 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include #include "virnetclientstream.h" #include "virnetclient.h" #include "viralloc.h" #include "virerror.h" #include "virlog.h" #include "virthread.h" #define VIR_FROM_THIS VIR_FROM_RPC VIR_LOG_INIT("rpc.netclientstream"); struct _virNetClientStream { virObjectLockable parent; virNetClientProgramPtr prog; int proc; unsigned serial; virError err; /* XXX this buffer is unbounded if the client * app has domain events registered, since packets * may be read off wire, while app isn't ready to * recv them. Figure out how to address this some * time by stopping consuming any incoming data * off the socket.... */ virNetMessagePtr rx; bool incomingEOF; virNetClientStreamClosed closed; bool allowSkip; long long holeLength; /* Size of incoming hole in stream. */ virNetClientStreamEventCallback cb; void *cbOpaque; virFreeCallback cbFree; int cbEvents; int cbTimer; int cbDispatch; }; static virClassPtr virNetClientStreamClass; static void virNetClientStreamDispose(void *obj); static int virNetClientStreamOnceInit(void) { if (!VIR_CLASS_NEW(virNetClientStream, virClassForObjectLockable())) return -1; return 0; } VIR_ONCE_GLOBAL_INIT(virNetClientStream); static void virNetClientStreamEventTimerUpdate(virNetClientStreamPtr st) { if (!st->cb) return; VIR_DEBUG("Check timer rx=%p cbEvents=%d", st->rx, st->cbEvents); if (((st->rx || st->incomingEOF || st->err.code != VIR_ERR_OK || st->closed) && (st->cbEvents & VIR_STREAM_EVENT_READABLE)) || (st->cbEvents & VIR_STREAM_EVENT_WRITABLE)) { VIR_DEBUG("Enabling event timer"); virEventUpdateTimeout(st->cbTimer, 0); } else { VIR_DEBUG("Disabling event timer"); virEventUpdateTimeout(st->cbTimer, -1); } } static void virNetClientStreamEventTimer(int timer G_GNUC_UNUSED, void *opaque) { virNetClientStreamPtr st = opaque; int events = 0; virObjectLock(st); if (st->cb && (st->cbEvents & VIR_STREAM_EVENT_READABLE) && (st->rx || st->incomingEOF || st->err.code != VIR_ERR_OK || st->closed)) events |= VIR_STREAM_EVENT_READABLE; if (st->cb && (st->cbEvents & VIR_STREAM_EVENT_WRITABLE)) events |= VIR_STREAM_EVENT_WRITABLE; VIR_DEBUG("Got Timer dispatch events=%d cbEvents=%d rx=%p", events, st->cbEvents, st->rx); if (events) { virNetClientStreamEventCallback cb = st->cb; void *cbOpaque = st->cbOpaque; virFreeCallback cbFree = st->cbFree; st->cbDispatch = 1; virObjectUnlock(st); (cb)(st, events, cbOpaque); virObjectLock(st); st->cbDispatch = 0; if (!st->cb && cbFree) (cbFree)(cbOpaque); } virObjectUnlock(st); } virNetClientStreamPtr virNetClientStreamNew(virNetClientProgramPtr prog, int proc, unsigned serial, bool allowSkip) { virNetClientStreamPtr st; if (virNetClientStreamInitialize() < 0) return NULL; if (!(st = virObjectLockableNew(virNetClientStreamClass))) return NULL; st->prog = virObjectRef(prog); st->proc = proc; st->serial = serial; st->allowSkip = allowSkip; return st; } void virNetClientStreamDispose(void *obj) { virNetClientStreamPtr st = obj; virResetError(&st->err); while (st->rx) { virNetMessagePtr msg = st->rx; virNetMessageQueueServe(&st->rx); virNetMessageFree(msg); } virObjectUnref(st->prog); } bool virNetClientStreamMatches(virNetClientStreamPtr st, virNetMessagePtr msg) { bool match = false; virObjectLock(st); if (virNetClientProgramMatches(st->prog, msg) && st->proc == msg->header.proc && st->serial == msg->header.serial) match = true; virObjectUnlock(st); return match; } static void virNetClientStreamRaiseError(virNetClientStreamPtr st) { virRaiseErrorFull(__FILE__, __FUNCTION__, __LINE__, st->err.domain, st->err.code, st->err.level, st->err.str1, st->err.str2, st->err.str3, st->err.int1, st->err.int2, "%s", st->err.message ? st->err.message : _("Unknown error")); } /* MUST be called under stream or client lock */ int virNetClientStreamCheckState(virNetClientStreamPtr st) { if (st->err.code != VIR_ERR_OK) { virNetClientStreamRaiseError(st); return -1; } if (st->closed) { virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("stream is closed")); return -1; } return 0; } /* MUST be called under stream or client lock. This should * be called only for message that expect reply. */ int virNetClientStreamCheckSendStatus(virNetClientStreamPtr st, virNetMessagePtr msg) { if (st->err.code != VIR_ERR_OK) { virNetClientStreamRaiseError(st); return -1; } /* We can not check if the message is dummy in a usual way * by checking msg->bufferLength because at this point message payload * is cleared. As caller must not call this function for messages * not expecting reply we can check for dummy messages just by status. */ if (msg->header.status == VIR_NET_CONTINUE) { if (st->closed) { virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("stream is closed")); return -1; } return 0; } else if (msg->header.status == VIR_NET_OK && st->closed != VIR_NET_CLIENT_STREAM_CLOSED_FINISHED) { virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("stream aborted by another thread")); return -1; } return 0; } void virNetClientStreamSetClosed(virNetClientStreamPtr st, virNetClientStreamClosed closed) { virObjectLock(st); st->closed = closed; virNetClientStreamEventTimerUpdate(st); virObjectUnlock(st); } int virNetClientStreamSetError(virNetClientStreamPtr st, virNetMessagePtr msg) { virNetMessageError err; int ret = -1; virObjectLock(st); if (st->err.code != VIR_ERR_OK) VIR_DEBUG("Overwriting existing stream error %s", NULLSTR(st->err.message)); virResetError(&st->err); memset(&err, 0, sizeof(err)); if (virNetMessageDecodePayload(msg, (xdrproc_t)xdr_virNetMessageError, &err) < 0) goto cleanup; if (err.domain == VIR_FROM_REMOTE && err.code == VIR_ERR_RPC && err.level == VIR_ERR_ERROR && err.message && STRPREFIX(*err.message, "unknown procedure")) { st->err.code = VIR_ERR_NO_SUPPORT; } else { st->err.code = err.code; } if (err.message) { st->err.message = *err.message; *err.message = NULL; } st->err.domain = err.domain; st->err.level = err.level; if (err.str1) { st->err.str1 = *err.str1; *err.str1 = NULL; } if (err.str2) { st->err.str2 = *err.str2; *err.str2 = NULL; } if (err.str3) { st->err.str3 = *err.str3; *err.str3 = NULL; } st->err.int1 = err.int1; st->err.int2 = err.int2; virNetClientStreamEventTimerUpdate(st); ret = 0; cleanup: xdr_free((xdrproc_t)xdr_virNetMessageError, (void*)&err); virObjectUnlock(st); return ret; } int virNetClientStreamQueuePacket(virNetClientStreamPtr st, virNetMessagePtr msg) { virNetMessagePtr tmp_msg; VIR_DEBUG("Incoming stream message: stream=%p message=%p", st, msg); if (msg->bufferLength == msg->bufferOffset) { /* No payload means end of the stream. */ virObjectLock(st); st->incomingEOF = true; virNetClientStreamEventTimerUpdate(st); virObjectUnlock(st); return 0; } /* Unfortunately, we must allocate new message as the one we * get in @msg is going to be cleared later in the process. */ if (!(tmp_msg = virNetMessageNew(false))) return -1; /* Copy header */ memcpy(&tmp_msg->header, &msg->header, sizeof(msg->header)); /* Steal message buffer */ tmp_msg->buffer = msg->buffer; tmp_msg->bufferLength = msg->bufferLength; tmp_msg->bufferOffset = msg->bufferOffset; msg->buffer = NULL; msg->bufferLength = msg->bufferOffset = 0; virObjectLock(st); /* Don't distinguish VIR_NET_STREAM and VIR_NET_STREAM_SKIP * here just yet. We want in order processing! */ virNetMessageQueuePush(&st->rx, tmp_msg); virNetClientStreamEventTimerUpdate(st); virObjectUnlock(st); 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; } else { if (virNetMessageEncodePayloadRaw(msg, NULL, 0) < 0) goto error; } if (virNetClientSendStream(client, msg, st) < 0) goto error; virNetMessageFree(msg); return nbytes; error: virNetMessageFree(msg); return -1; } static int virNetClientStreamSetHole(virNetClientStreamPtr st, long long length, unsigned int flags) { virCheckFlags(0, -1); virCheckPositiveArgReturn(length, -1); /* Shouldn't happen, But it's better to safe than sorry. */ if (st->holeLength) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unprocessed hole of size %lld already in the queue"), st->holeLength); return -1; } st->holeLength += length; return 0; } /** * virNetClientStreamHandleHole: * @client: client * @st: stream * * Called whenever current message processed in the stream is * VIR_NET_STREAM_HOLE. The stream @st is expected to be locked * already. * * Returns: 0 on success, * -1 otherwise. */ static int virNetClientStreamHandleHole(virNetClientPtr client, virNetClientStreamPtr st) { virNetMessagePtr msg; virNetStreamHole data; int ret = -1; VIR_DEBUG("client=%p st=%p", client, st); msg = st->rx; memset(&data, 0, sizeof(data)); /* We should not be called unless there's VIR_NET_STREAM_HOLE * message at the head of the list. But doesn't hurt to check */ if (!msg) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("No message in the queue")); goto cleanup; } if (msg->header.type != VIR_NET_STREAM_HOLE) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid message prog=%d type=%d serial=%u proc=%d"), msg->header.prog, msg->header.type, msg->header.serial, msg->header.proc); goto cleanup; } /* Server should not send us VIR_NET_STREAM_HOLE unless we * have requested so. But does not hurt to check ... */ if (!st->allowSkip) { virReportError(VIR_ERR_RPC, "%s", _("Unexpected stream hole")); goto cleanup; } if (virNetMessageDecodePayload(msg, (xdrproc_t)xdr_virNetStreamHole, &data) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Malformed stream hole packet")); goto cleanup; } virNetMessageQueueServe(&st->rx); virNetMessageFree(msg); if (virNetClientStreamSetHole(st, data.length, data.flags) < 0) goto cleanup; ret = 0; cleanup: if (ret < 0) { /* Abort stream? */ } return ret; } int virNetClientStreamRecvPacket(virNetClientStreamPtr st, virNetClientPtr client, char *data, size_t nbytes, bool nonblock, unsigned int flags) { int rv = -1; size_t want; VIR_DEBUG("st=%p client=%p data=%p nbytes=%zu nonblock=%d flags=0x%x", st, client, data, nbytes, nonblock, flags); virCheckFlags(VIR_STREAM_RECV_STOP_AT_HOLE, -1); virObjectLock(st); reread: if (virNetClientStreamCheckState(st) < 0) goto cleanup; if (!st->rx && !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 = virNetClientSendStream(client, msg, st); virObjectLock(st); virNetMessageFree(msg); if (ret < 0) goto cleanup; } VIR_DEBUG("After IO rx=%p", st->rx); if (st->rx && st->rx->header.type == VIR_NET_STREAM_HOLE && st->holeLength == 0) { /* Handle skip sent to us by server. */ if (virNetClientStreamHandleHole(client, st) < 0) goto cleanup; } if (!st->rx && !st->incomingEOF && st->holeLength == 0) { if (nonblock) { VIR_DEBUG("Non-blocking mode and no data available"); rv = -2; goto cleanup; } /* We have consumed all packets from incoming queue but those * were only skip packets, no data. Read the stream again. */ goto reread; } want = nbytes; if (st->holeLength) { /* Pretend holeLength zeroes was read from stream. */ size_t len = want; /* Yes, pretend unless we are asked not to. */ if (flags & VIR_STREAM_RECV_STOP_AT_HOLE) { /* No error reporting here. Caller knows what they are doing. */ rv = -3; goto cleanup; } if (len > st->holeLength) len = st->holeLength; memset(data, 0, len); st->holeLength -= len; want -= len; } while (want && st->rx && st->rx->header.type == VIR_NET_STREAM) { virNetMessagePtr msg = st->rx; size_t len = want; if (len > msg->bufferLength - msg->bufferOffset) len = msg->bufferLength - msg->bufferOffset; if (!len) break; memcpy(data + (nbytes - want), msg->buffer + msg->bufferOffset, len); want -= len; msg->bufferOffset += len; if (msg->bufferOffset == msg->bufferLength) { virNetMessageQueueServe(&st->rx); virNetMessageFree(msg); } } rv = nbytes - want; virNetClientStreamEventTimerUpdate(st); cleanup: virObjectUnlock(st); return rv; } int virNetClientStreamSendHole(virNetClientStreamPtr st, virNetClientPtr client, long long length, unsigned int flags) { virNetMessagePtr msg = NULL; virNetStreamHole data; int ret = -1; VIR_DEBUG("st=%p length=%llu", st, length); if (!st->allowSkip) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("Skipping is not supported with this stream")); return -1; } memset(&data, 0, sizeof(data)); data.length = length; data.flags = flags; if (!(msg = virNetMessageNew(false))) return -1; virObjectLock(st); msg->header.prog = virNetClientProgramGetProgram(st->prog); msg->header.vers = virNetClientProgramGetVersion(st->prog); msg->header.status = VIR_NET_CONTINUE; msg->header.type = VIR_NET_STREAM_HOLE; msg->header.serial = st->serial; msg->header.proc = st->proc; virObjectUnlock(st); if (virNetMessageEncodeHeader(msg) < 0) goto cleanup; if (virNetMessageEncodePayload(msg, (xdrproc_t)xdr_virNetStreamHole, &data) < 0) goto cleanup; if (virNetClientSendStream(client, msg, st) < 0) goto cleanup; ret = 0; cleanup: virNetMessageFree(msg); return ret; } int virNetClientStreamRecvHole(virNetClientPtr client G_GNUC_UNUSED, virNetClientStreamPtr st, long long *length) { if (!st->allowSkip) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("Holes are not supported with this stream")); return -1; } virObjectLock(st); if (virNetClientStreamCheckState(st) < 0) { virObjectUnlock(st); return -1; } *length = st->holeLength; st->holeLength = 0; virObjectUnlock(st); return 0; } int virNetClientStreamEventAddCallback(virNetClientStreamPtr st, int events, virNetClientStreamEventCallback cb, void *opaque, virFreeCallback ff) { int ret = -1; virObjectLock(st); if (st->cb) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("multiple stream callbacks not supported")); goto cleanup; } virObjectRef(st); if ((st->cbTimer = virEventAddTimeout(-1, virNetClientStreamEventTimer, st, virObjectFreeCallback)) < 0) { virObjectUnref(st); goto cleanup; } st->cb = cb; st->cbOpaque = opaque; st->cbFree = ff; st->cbEvents = events; virNetClientStreamEventTimerUpdate(st); ret = 0; cleanup: virObjectUnlock(st); return ret; } int virNetClientStreamEventUpdateCallback(virNetClientStreamPtr st, int events) { int ret = -1; virObjectLock(st); if (!st->cb) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no stream callback registered")); goto cleanup; } st->cbEvents = events; virNetClientStreamEventTimerUpdate(st); ret = 0; cleanup: virObjectUnlock(st); return ret; } int virNetClientStreamEventRemoveCallback(virNetClientStreamPtr st) { int ret = -1; virObjectLock(st); if (!st->cb) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no stream callback registered")); goto cleanup; } if (!st->cbDispatch && st->cbFree) (st->cbFree)(st->cbOpaque); st->cb = NULL; st->cbOpaque = NULL; st->cbFree = NULL; st->cbEvents = 0; virEventRemoveTimeout(st->cbTimer); ret = 0; cleanup: virObjectUnlock(st); return ret; } bool virNetClientStreamEOF(virNetClientStreamPtr st) { return st->incomingEOF; }