diff options
Diffstat (limited to '')
-rw-r--r-- | src/libs/xpcom18a4/ipc/ipcd/client/src/ipcdclient.cpp | 1505 |
1 files changed, 1505 insertions, 0 deletions
diff --git a/src/libs/xpcom18a4/ipc/ipcd/client/src/ipcdclient.cpp b/src/libs/xpcom18a4/ipc/ipcd/client/src/ipcdclient.cpp new file mode 100644 index 00000000..ce0f86c8 --- /dev/null +++ b/src/libs/xpcom18a4/ipc/ipcd/client/src/ipcdclient.cpp @@ -0,0 +1,1505 @@ +/* vim:set ts=2 sw=2 et cindent: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla IPC. + * + * The Initial Developer of the Original Code is IBM Corporation. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher <darin@meer.net> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "ipcdclient.h" +#include "ipcConnection.h" +#include "ipcConfig.h" +#include "ipcMessageQ.h" +#include "ipcMessageUtils.h" +#include "ipcLog.h" +#include "ipcm.h" + +#include "nsIFile.h" +#include "nsEventQueueUtils.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsRefPtrHashtable.h" +#include "nsAutoLock.h" +#include "nsProxyRelease.h" +#include "nsCOMArray.h" + +#include "prio.h" +#include "prproces.h" +#include "pratom.h" + +#ifdef VBOX +# include <iprt/critsect.h> +# define VBOX_WITH_IPCCLIENT_RW_CS +#endif + +/* ------------------------------------------------------------------------- */ + +#define IPC_REQUEST_TIMEOUT PR_SecondsToInterval(30) + +/* ------------------------------------------------------------------------- */ + +class ipcTargetData +{ +public: + static NS_HIDDEN_(ipcTargetData*) Create(); + + // threadsafe addref/release + NS_HIDDEN_(nsrefcnt) AddRef() { return PR_AtomicIncrement(&refcnt); } + NS_HIDDEN_(nsrefcnt) Release() { PRInt32 r = PR_AtomicDecrement(&refcnt); if (r == 0) delete this; return r; } + + NS_HIDDEN_(void) SetObserver(ipcIMessageObserver *aObserver, PRBool aOnCurrentThread); + + // protects access to the members of this class + PRMonitor *monitor; + + // this may be null + nsCOMPtr<ipcIMessageObserver> observer; + + // the message observer is called via this event queue + nsCOMPtr<nsIEventQueue> eventQ; + + // incoming messages are added to this list + ipcMessageQ pendingQ; + + // non-zero if the observer has been disabled (this means that new messages + // should not be dispatched to the observer until the observer is re-enabled + // via IPC_EnableMessageObserver). + PRInt32 observerDisabled; + +private: + + ipcTargetData() + : monitor(nsAutoMonitor::NewMonitor("ipcTargetData")) + , observerDisabled(0) + , refcnt(0) + {} + + ~ipcTargetData() + { + if (monitor) + nsAutoMonitor::DestroyMonitor(monitor); + } + + PRInt32 refcnt; +}; + +ipcTargetData * +ipcTargetData::Create() +{ + ipcTargetData *td = new ipcTargetData; + if (!td) + return NULL; + + if (!td->monitor) + { + delete td; + return NULL; + } + return td; +} + +void +ipcTargetData::SetObserver(ipcIMessageObserver *aObserver, PRBool aOnCurrentThread) +{ + observer = aObserver; + + if (aOnCurrentThread) + NS_GetCurrentEventQ(getter_AddRefs(eventQ)); + else + eventQ = nsnull; +} + +/* ------------------------------------------------------------------------- */ + +typedef nsRefPtrHashtable<nsIDHashKey, ipcTargetData> ipcTargetMap; + +class ipcClientState +{ +public: + static NS_HIDDEN_(ipcClientState *) Create(); + + ~ipcClientState() + { +#ifndef VBOX_WITH_IPCCLIENT_RW_CS + if (monitor) + nsAutoMonitor::DestroyMonitor(monitor); +#else + RTCritSectRwDelete(&critSect); +#endif + } + +#ifndef VBOX_WITH_IPCCLIENT_RW_CS + // + // the monitor protects the targetMap and the connected and shutdown flags. + // + // NOTE: we use a PRMonitor for this instead of a PRLock because we need + // the lock to be re-entrant. since we don't ever need to wait on + // this monitor, it might be worth it to implement a re-entrant + // wrapper for PRLock. + // + PRMonitor *monitor; +#else /* VBOX_WITH_IPCCLIENT_RW_CS */ + RTCRITSECTRW critSect; +#endif /* VBOX_WITH_IPCCLIENT_RW_CS */ + ipcTargetMap targetMap; + PRBool connected; + PRBool shutdown; + + // our process's client id + PRUint32 selfID; + + nsCOMArray<ipcIClientObserver> clientObservers; + +private: + + ipcClientState() +#ifndef VBOX_WITH_IPCCLIENT_RW_CS + : monitor(nsAutoMonitor::NewMonitor("ipcClientState")) + , connected(PR_FALSE) +#else + : connected(PR_FALSE) +#endif + , shutdown(PR_FALSE) + , selfID(0) + { +#ifdef VBOX_WITH_IPCCLIENT_RW_CS + /* Not employing the lock validator here to keep performance up in debug builds. */ + RTCritSectRwInitEx(&critSect, RTCRITSECT_FLAGS_NO_LOCK_VAL, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL); +#endif + } +}; + +ipcClientState * +ipcClientState::Create() +{ + ipcClientState *cs = new ipcClientState; + if (!cs) + return NULL; + +#ifndef VBOX_WITH_IPCCLIENT_RW_CS + if (!cs->monitor || !cs->targetMap.Init()) +#else + if (!RTCritSectRwIsInitialized(&cs->critSect) || !cs->targetMap.Init()) +#endif + { + delete cs; + return NULL; + } + + return cs; +} + +/* ------------------------------------------------------------------------- */ + +static ipcClientState *gClientState; + +static PRBool +GetTarget(const nsID &aTarget, ipcTargetData **td) +{ +#ifndef VBOX_WITH_IPCCLIENT_RW_CS + nsAutoMonitor mon(gClientState->monitor); + return gClientState->targetMap.Get(nsIDHashKey(&aTarget).GetKey(), td); +#else + RTCritSectRwEnterShared(&gClientState->critSect); + PRBool fRc = gClientState->targetMap.Get(nsIDHashKey(&aTarget).GetKey(), td); + RTCritSectRwLeaveShared(&gClientState->critSect); + return fRc; +#endif +} + +static PRBool +PutTarget(const nsID &aTarget, ipcTargetData *td) +{ +#ifndef VBOX_WITH_IPCCLIENT_RW_CS + nsAutoMonitor mon(gClientState->monitor); + return gClientState->targetMap.Put(nsIDHashKey(&aTarget).GetKey(), td); +#else + RTCritSectRwEnterExcl(&gClientState->critSect); + PRBool fRc = gClientState->targetMap.Put(nsIDHashKey(&aTarget).GetKey(), td); + RTCritSectRwLeaveExcl(&gClientState->critSect); + return fRc; +#endif +} + +static void +DelTarget(const nsID &aTarget) +{ +#ifndef VBOX_WITH_IPCCLIENT_RW_CS + nsAutoMonitor mon(gClientState->monitor); + gClientState->targetMap.Remove(nsIDHashKey(&aTarget).GetKey()); +#else + RTCritSectRwEnterExcl(&gClientState->critSect); + gClientState->targetMap.Remove(nsIDHashKey(&aTarget).GetKey()); + RTCritSectRwLeaveExcl(&gClientState->critSect); +#endif +} + +/* ------------------------------------------------------------------------- */ + +static nsresult +GetDaemonPath(nsCString &dpath) +{ + nsCOMPtr<nsIFile> file; + + nsresult rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, + getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) + { + rv = file->AppendNative(NS_LITERAL_CSTRING(IPC_DAEMON_APP_NAME)); + if (NS_SUCCEEDED(rv)) + rv = file->GetNativePath(dpath); + } + + return rv; +} + +/* ------------------------------------------------------------------------- */ + +static void +ProcessPendingQ(const nsID &aTarget) +{ + ipcMessageQ tempQ; + + nsRefPtr<ipcTargetData> td; + if (GetTarget(aTarget, getter_AddRefs(td))) + { + nsAutoMonitor mon(td->monitor); + + // if the observer for this target has been temporarily disabled, then + // we must not processing any pending messages at this time. + + if (!td->observerDisabled) + td->pendingQ.MoveTo(tempQ); + } + + // process pending queue outside monitor + while (!tempQ.IsEmpty()) + { + ipcMessage *msg = tempQ.First(); + + // it is possible that messages for other targets are in the queue + // (currently, this can be only a IPCM_MSG_PSH_CLIENT_STATE message + // initially addressed to IPCM_TARGET, see IPC_OnMessageAvailable()) + // --ignore them. + if (td->observer && msg->Target().Equals(aTarget)) + td->observer->OnMessageAvailable(msg->mMetaData, + msg->Target(), + (const PRUint8 *) msg->Data(), + msg->DataLen()); + else + { + // the IPCM target does not have an observer, and therefore any IPCM + // messages that make it here will simply be dropped. + NS_ASSERTION(aTarget.Equals(IPCM_TARGET) || msg->Target().Equals(IPCM_TARGET), + "unexpected target"); + LOG(("dropping IPCM message: type=%x\n", IPCM_GetType(msg))); + } + tempQ.DeleteFirst(); + } +} + +/* ------------------------------------------------------------------------- */ + +// WaitTarget enables support for multiple threads blocking on the same +// message target. the selector is called while inside the target's monitor. + +typedef nsresult (* ipcMessageSelector)( + void *arg, + ipcTargetData *td, + const ipcMessage *msg +); + +// selects any +static nsresult +DefaultSelector(void *arg, ipcTargetData *td, const ipcMessage *msg) +{ + return NS_OK; +} + +static nsresult +WaitTarget(const nsID &aTarget, + PRIntervalTime aTimeout, + ipcMessage **aMsg, + ipcMessageSelector aSelector = nsnull, + void *aArg = nsnull) +{ + *aMsg = nsnull; + + if (!aSelector) + aSelector = DefaultSelector; + + nsRefPtr<ipcTargetData> td; + if (!GetTarget(aTarget, getter_AddRefs(td))) + return NS_ERROR_INVALID_ARG; // bad aTarget + + PRBool isIPCMTarget = aTarget.Equals(IPCM_TARGET); + + PRIntervalTime timeStart = PR_IntervalNow(); + PRIntervalTime timeEnd; + if (aTimeout == PR_INTERVAL_NO_TIMEOUT) + timeEnd = aTimeout; + else if (aTimeout == PR_INTERVAL_NO_WAIT) + timeEnd = timeStart; + else + { + timeEnd = timeStart + aTimeout; + + // if overflowed, then set to max value + if (timeEnd < timeStart) + timeEnd = PR_INTERVAL_NO_TIMEOUT; + } + + ipcMessage *lastChecked = nsnull, *beforeLastChecked = nsnull; + nsresult rv = NS_ERROR_ABORT; + + nsAutoMonitor mon(td->monitor); + + // only the ICPM target is allowed to wait for a message after shutdown + // (but before disconnection). this gives client observers called from + // IPC_Shutdown a chance to use IPC_SendMessage to send necessary + // "last minute" messages to other clients. + + while (gClientState->connected && (!gClientState->shutdown || isIPCMTarget)) + { + NS_ASSERTION(!lastChecked, "oops"); + + // + // NOTE: + // + // we must start at the top of the pending queue, possibly revisiting + // messages that our selector has already rejected. this is necessary + // because the queue may have been modified while we were waiting on + // the monitor. the impact of this on performance remains to be seen. + // + // one cheap solution is to keep a counter that is incremented each + // time a message is removed from the pending queue. that way we can + // avoid revisiting all messages sometimes. + // + + lastChecked = td->pendingQ.First(); + beforeLastChecked = nsnull; + + // loop over pending queue until we find a message that our selector likes. + while (lastChecked) + { + // + // it is possible that this call to WaitTarget() has been initiated by + // some other selector, that might be currently processing the same + // message (since the message remains in the queue until the selector + // returns TRUE). here we prevent this situation by using a special flag + // to guarantee that every message is processed only once. + // + + if (!lastChecked->TestFlag(IPC_MSG_FLAG_IN_PROCESS)) + { + lastChecked->SetFlag(IPC_MSG_FLAG_IN_PROCESS); + nsresult acceptedRV = (aSelector)(aArg, td, lastChecked); + lastChecked->ClearFlag(IPC_MSG_FLAG_IN_PROCESS); + + if (acceptedRV != IPC_WAIT_NEXT_MESSAGE) + { + if (acceptedRV == NS_OK) + { + // remove from pending queue + if (beforeLastChecked) + td->pendingQ.RemoveAfter(beforeLastChecked); + else + td->pendingQ.RemoveFirst(); + + lastChecked->mNext = nsnull; + *aMsg = lastChecked; + break; + } + else /* acceptedRV == IPC_DISCARD_MESSAGE */ + { + ipcMessage *nextToCheck = lastChecked->mNext; + + // discard from pending queue + if (beforeLastChecked) + td->pendingQ.DeleteAfter(beforeLastChecked); + else + td->pendingQ.DeleteFirst(); + + lastChecked = nextToCheck; + + continue; + } + } + } + + beforeLastChecked = lastChecked; + lastChecked = lastChecked->mNext; + } + + if (*aMsg) + { + rv = NS_OK; + break; + } +#ifdef VBOX + else + { + /* Special client liveness check if there is no message to process. + * This is necessary as there might be several threads waiting for + * a message from a single client, and only one gets the DOWN msg. */ + nsresult aliveRV = (aSelector)(aArg, td, NULL); + if (aliveRV != IPC_WAIT_NEXT_MESSAGE) + { + *aMsg = NULL; + break; + } + } +#endif /* VBOX */ + + PRIntervalTime t = PR_IntervalNow(); + if (t > timeEnd) // check if timeout has expired + { + rv = IPC_ERROR_WOULD_BLOCK; + break; + } + mon.Wait(timeEnd - t); + + LOG(("woke up from sleep [pendingQempty=%d connected=%d shutdown=%d isIPCMTarget=%d]\n", + td->pendingQ.IsEmpty(), gClientState->connected, + gClientState->shutdown, isIPCMTarget)); + } + + return rv; +} + +/* ------------------------------------------------------------------------- */ + +static void +PostEvent(nsIEventTarget *eventTarget, PLEvent *ev) +{ + if (!ev) + return; + + nsresult rv = eventTarget->PostEvent(ev); + if (NS_FAILED(rv)) + { + NS_WARNING("PostEvent failed"); + PL_DestroyEvent(ev); + } +} + +static void +PostEventToMainThread(PLEvent *ev) +{ + nsCOMPtr<nsIEventQueue> eventQ; + NS_GetMainEventQ(getter_AddRefs(eventQ)); + if (!eventQ) + { + NS_WARNING("unable to get reference to main event queue"); + PL_DestroyEvent(ev); + return; + } + PostEvent(eventQ, ev); +} + +/* ------------------------------------------------------------------------- */ + +class ipcEvent_ClientState : public PLEvent +{ +public: + ipcEvent_ClientState(PRUint32 aClientID, PRUint32 aClientState) + : mClientID(aClientID) + , mClientState(aClientState) + { + PL_InitEvent(this, nsnull, HandleEvent, DestroyEvent); + } + + PR_STATIC_CALLBACK(void *) HandleEvent(PLEvent *ev) + { + // maybe we've been shutdown! + if (!gClientState) + return nsnull; + + ipcEvent_ClientState *self = (ipcEvent_ClientState *) ev; + + for (PRInt32 i=0; i<gClientState->clientObservers.Count(); ++i) + gClientState->clientObservers[i]->OnClientStateChange(self->mClientID, + self->mClientState); + return nsnull; + } + + PR_STATIC_CALLBACK(void) DestroyEvent(PLEvent *ev) + { + delete (ipcEvent_ClientState *) ev; + } + +private: + PRUint32 mClientID; + PRUint32 mClientState; +}; + +/* ------------------------------------------------------------------------- */ + +class ipcEvent_ProcessPendingQ : public PLEvent +{ +public: + ipcEvent_ProcessPendingQ(const nsID &aTarget) + : mTarget(aTarget) + { + PL_InitEvent(this, nsnull, HandleEvent, DestroyEvent); + } + + PR_STATIC_CALLBACK(void *) HandleEvent(PLEvent *ev) + { + ProcessPendingQ(((ipcEvent_ProcessPendingQ *) ev)->mTarget); + return nsnull; + } + + PR_STATIC_CALLBACK(void) DestroyEvent(PLEvent *ev) + { + delete (ipcEvent_ProcessPendingQ *) ev; + } + +private: + const nsID mTarget; +}; + +static void +CallProcessPendingQ(const nsID &target, ipcTargetData *td) +{ + // we assume that we are inside td's monitor + + PLEvent *ev = new ipcEvent_ProcessPendingQ(target); + if (!ev) + return; + + nsresult rv; + + if (td->eventQ) + rv = td->eventQ->PostEvent(ev); + else + rv = IPC_DoCallback((ipcCallbackFunc) PL_HandleEvent, ev); + + if (NS_FAILED(rv)) + PL_DestroyEvent(ev); +} + +/* ------------------------------------------------------------------------- */ + +static void +DisableMessageObserver(const nsID &aTarget) +{ + nsRefPtr<ipcTargetData> td; + if (GetTarget(aTarget, getter_AddRefs(td))) + { + nsAutoMonitor mon(td->monitor); + ++td->observerDisabled; + } +} + +static void +EnableMessageObserver(const nsID &aTarget) +{ + nsRefPtr<ipcTargetData> td; + if (GetTarget(aTarget, getter_AddRefs(td))) + { + nsAutoMonitor mon(td->monitor); + if (td->observerDisabled > 0 && --td->observerDisabled == 0) + if (!td->pendingQ.IsEmpty()) + CallProcessPendingQ(aTarget, td); + } +} + +/* ------------------------------------------------------------------------- */ + +// converts IPCM_ERROR_* status codes to NS_ERROR_* status codes +static nsresult nsresult_from_ipcm_result(PRInt32 status) +{ + nsresult rv = NS_ERROR_FAILURE; + + switch (status) + { + case IPCM_ERROR_GENERIC: rv = NS_ERROR_FAILURE; break; + case IPCM_ERROR_INVALID_ARG: rv = NS_ERROR_INVALID_ARG; break; + case IPCM_ERROR_NO_CLIENT: rv = NS_ERROR_CALL_FAILED; break; + // TODO: select better mapping for the below codes + case IPCM_ERROR_NO_SUCH_DATA: + case IPCM_ERROR_ALREADY_EXISTS: rv = NS_ERROR_FAILURE; break; + default: NS_ASSERTION(PR_FALSE, "No conversion"); + } + + return rv; +} + +/* ------------------------------------------------------------------------- */ + +// selects the next IPCM message with matching request index +static nsresult +WaitIPCMResponseSelector(void *arg, ipcTargetData *td, const ipcMessage *msg) +{ +#ifdef VBOX + if (!msg) + return IPC_WAIT_NEXT_MESSAGE; +#endif /* VBOX */ + PRUint32 requestIndex = *(PRUint32 *) arg; + return IPCM_GetRequestIndex(msg) == requestIndex ? NS_OK : IPC_WAIT_NEXT_MESSAGE; +} + +// wait for an IPCM response message. if responseMsg is null, then it is +// assumed that the caller does not care to get a reference to the +// response itself. if the response is an IPCM_MSG_ACK_RESULT, then the +// status code is mapped to a nsresult and returned by this function. +static nsresult +WaitIPCMResponse(PRUint32 requestIndex, ipcMessage **responseMsg = nsnull) +{ + ipcMessage *msg; + + nsresult rv = WaitTarget(IPCM_TARGET, IPC_REQUEST_TIMEOUT, &msg, + WaitIPCMResponseSelector, &requestIndex); + if (NS_FAILED(rv)) + return rv; + + if (IPCM_GetType(msg) == IPCM_MSG_ACK_RESULT) + { + ipcMessageCast<ipcmMessageResult> result(msg); + if (result->Status() < 0) + rv = nsresult_from_ipcm_result(result->Status()); + else + rv = NS_OK; + } + + if (responseMsg) + *responseMsg = msg; + else + delete msg; + + return rv; +} + +// make an IPCM request and wait for a response. +static nsresult +MakeIPCMRequest(ipcMessage *msg, ipcMessage **responseMsg = nsnull) +{ + if (!msg) + return NS_ERROR_OUT_OF_MEMORY; + + PRUint32 requestIndex = IPCM_GetRequestIndex(msg); + + // suppress 'ProcessPendingQ' for IPCM messages until we receive the + // response to this IPCM request. if we did not do this then there + // would be a race condition leading to the possible removal of our + // response from the pendingQ between sending the request and waiting + // for the response. + DisableMessageObserver(IPCM_TARGET); + + nsresult rv = IPC_SendMsg(msg); + if (NS_SUCCEEDED(rv)) + rv = WaitIPCMResponse(requestIndex, responseMsg); + + EnableMessageObserver(IPCM_TARGET); + return rv; +} + +/* ------------------------------------------------------------------------- */ + +static void +RemoveTarget(const nsID &aTarget, PRBool aNotifyDaemon) +{ + DelTarget(aTarget); + + if (aNotifyDaemon) + { + nsresult rv = MakeIPCMRequest(new ipcmMessageClientDelTarget(aTarget)); + if (NS_FAILED(rv)) + LOG(("failed to delete target: rv=%x\n", rv)); + } +} + +static nsresult +DefineTarget(const nsID &aTarget, + ipcIMessageObserver *aObserver, + PRBool aOnCurrentThread, + PRBool aNotifyDaemon, + ipcTargetData **aResult) +{ + nsresult rv; + + nsRefPtr<ipcTargetData> td( ipcTargetData::Create() ); + if (!td) + return NS_ERROR_OUT_OF_MEMORY; + td->SetObserver(aObserver, aOnCurrentThread); + + if (!PutTarget(aTarget, td)) + return NS_ERROR_OUT_OF_MEMORY; + + if (aNotifyDaemon) + { + rv = MakeIPCMRequest(new ipcmMessageClientAddTarget(aTarget)); + if (NS_FAILED(rv)) + { + LOG(("failed to add target: rv=%x\n", rv)); + RemoveTarget(aTarget, PR_FALSE); + return rv; + } + } + + if (aResult) + NS_ADDREF(*aResult = td); + return NS_OK; +} + +/* ------------------------------------------------------------------------- */ + +static nsresult +TryConnect() +{ + nsCAutoString dpath; + nsresult rv = GetDaemonPath(dpath); + if (NS_FAILED(rv)) + return rv; + + rv = IPC_Connect(dpath.get()); + if (NS_FAILED(rv)) + return rv; + + gClientState->connected = PR_TRUE; + + rv = DefineTarget(IPCM_TARGET, nsnull, PR_FALSE, PR_FALSE, nsnull); + if (NS_FAILED(rv)) + return rv; + + ipcMessage *msg = NULL; + + // send CLIENT_HELLO and wait for CLIENT_ID response... + rv = MakeIPCMRequest(new ipcmMessageClientHello(), &msg); + if (NS_FAILED(rv)) + { +#ifdef VBOX /* MakeIPCMRequest may return a failure (e.g. NS_ERROR_CALL_FAILED) and a response msg. */ + if (msg) + delete msg; +#endif + return rv; + } + + if (IPCM_GetType(msg) == IPCM_MSG_ACK_CLIENT_ID) + gClientState->selfID = ipcMessageCast<ipcmMessageClientID>(msg)->ClientID(); + else + { + LOG(("unexpected response from CLIENT_HELLO message: type=%x!\n", + IPCM_GetType(msg))); + rv = NS_ERROR_UNEXPECTED; + } + + delete msg; + return rv; +} + +nsresult +IPC_Init() +{ + NS_ENSURE_TRUE(!gClientState, NS_ERROR_ALREADY_INITIALIZED); + + IPC_InitLog(">>>"); + + gClientState = ipcClientState::Create(); + if (!gClientState) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = TryConnect(); + if (NS_FAILED(rv)) + IPC_Shutdown(); + + return rv; +} + +PR_STATIC_CALLBACK(PLDHashOperator) +EnumerateTargetMapAndNotify(const nsID &aKey, + ipcTargetData *aData, + void *aClosure); + +nsresult +IPC_Shutdown() +{ + NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED); + + LOG(("IPC_Shutdown: connected=%d\n",gClientState->connected)); + + if (gClientState->connected) + { + { + // first, set the shutdown flag and unblock any calls to WaitTarget. + // all targets but IPCM will not be able to use WaitTarget any more. + +#ifndef VBOX_WITH_IPCCLIENT_RW_CS + nsAutoMonitor mon(gClientState->monitor); +#else + RTCritSectRwEnterExcl(&gClientState->critSect); +#endif + + gClientState->shutdown = PR_TRUE; + gClientState->targetMap.EnumerateRead(EnumerateTargetMapAndNotify, nsnull); + +#ifdef VBOX_WITH_IPCCLIENT_RW_CS + RTCritSectRwLeaveExcl(&gClientState->critSect); +#endif + } + + // inform all client observers that we're being shutdown to let interested + // parties gracefully uninitialize themselves. the IPCM target is still + // fully operational at this point, so they can use IPC_SendMessage + // (this is essential for the DConnect extension, for example, to do the + // proper uninitialization). + + ipcEvent_ClientState *ev = new ipcEvent_ClientState(IPC_SENDER_ANY, + IPCM_CLIENT_STATE_DOWN); + ipcEvent_ClientState::HandleEvent (ev); + ipcEvent_ClientState::DestroyEvent (ev); + + IPC_Disconnect(); + } + + // + // make gClientState nsnull before deletion to cause all public IPC_* + // calls (possibly made during ipcClientState destruction) to return + // NS_ERROR_NOT_INITIALIZED. + // + // NOTE: isn't just checking for gClientState->connected in every appropriate + // IPC_* method a better solution? + // + ipcClientState *aClientState = gClientState; + gClientState = nsnull; + delete aClientState; + + return NS_OK; +} + +/* ------------------------------------------------------------------------- */ + +nsresult +IPC_DefineTarget(const nsID &aTarget, + ipcIMessageObserver *aObserver, + PRBool aOnCurrentThread) +{ + NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED); + + // do not permit the re-definition of the IPCM protocol's target. + if (aTarget.Equals(IPCM_TARGET)) + return NS_ERROR_INVALID_ARG; + + nsresult rv; + + nsRefPtr<ipcTargetData> td; + if (GetTarget(aTarget, getter_AddRefs(td))) + { + // clear out observer before removing target since we want to ensure that + // the observer is released on the main thread. + { + nsAutoMonitor mon(td->monitor); + td->SetObserver(aObserver, aOnCurrentThread); + } + + // remove target outside of td's monitor to avoid holding the monitor + // while entering the client state's monitor. + if (!aObserver) + RemoveTarget(aTarget, PR_TRUE); + + rv = NS_OK; + } + else + { + if (aObserver) + rv = DefineTarget(aTarget, aObserver, aOnCurrentThread, PR_TRUE, nsnull); + else + rv = NS_ERROR_INVALID_ARG; // unknown target + } + + return rv; +} + +nsresult +IPC_DisableMessageObserver(const nsID &aTarget) +{ + NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED); + + // do not permit modifications to the IPCM protocol's target. + if (aTarget.Equals(IPCM_TARGET)) + return NS_ERROR_INVALID_ARG; + + DisableMessageObserver(aTarget); + return NS_OK; +} + +nsresult +IPC_EnableMessageObserver(const nsID &aTarget) +{ + NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED); + + // do not permit modifications to the IPCM protocol's target. + if (aTarget.Equals(IPCM_TARGET)) + return NS_ERROR_INVALID_ARG; + + EnableMessageObserver(aTarget); + return NS_OK; +} + +nsresult +IPC_SendMessage(PRUint32 aReceiverID, + const nsID &aTarget, + const PRUint8 *aData, + PRUint32 aDataLen) +{ + NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED); + + // do not permit sending IPCM messages + if (aTarget.Equals(IPCM_TARGET)) + return NS_ERROR_INVALID_ARG; + + nsresult rv; + if (aReceiverID == 0) + { + ipcMessage *msg = new ipcMessage(aTarget, (const char *) aData, aDataLen); + if (!msg) + return NS_ERROR_OUT_OF_MEMORY; + + rv = IPC_SendMsg(msg); + } + else + rv = MakeIPCMRequest(new ipcmMessageForward(IPCM_MSG_REQ_FORWARD, + aReceiverID, + aTarget, + (const char *) aData, + aDataLen)); + + return rv; +} + +struct WaitMessageSelectorData +{ + PRUint32 senderID; + ipcIMessageObserver *observer; + PRBool senderDead; +}; + +static nsresult WaitMessageSelector(void *arg, ipcTargetData *td, const ipcMessage *msg) +{ + WaitMessageSelectorData *data = (WaitMessageSelectorData *) arg; +#ifdef VBOX + if (!msg) + { + /* Special NULL message which asks to check whether the client is + * still alive. Called when there is nothing suitable in the queue. */ + ipcIMessageObserver *obs = data->observer; + if (!obs) + obs = td->observer; + NS_ASSERTION(obs, "must at least have a default observer"); + + nsresult rv = obs->OnMessageAvailable(IPC_SENDER_ANY, nsID(), 0, 0); + if (rv != IPC_WAIT_NEXT_MESSAGE) + data->senderDead = PR_TRUE; + + return rv; + } +#endif /* VBOX */ + + // process the specially forwarded client state message to see if the + // sender we're waiting a message from has died. + + if (msg->Target().Equals(IPCM_TARGET)) + { + switch (IPCM_GetType(msg)) + { + case IPCM_MSG_PSH_CLIENT_STATE: + { + ipcMessageCast<ipcmMessageClientState> status(msg); + if ((data->senderID == IPC_SENDER_ANY || + status->ClientID() == data->senderID) && + status->ClientState() == IPCM_CLIENT_STATE_DOWN) + { + LOG(("sender (%d) we're waiting a message from (%d) has died\n", + status->ClientID(), data->senderID)); + + if (data->senderID != IPC_SENDER_ANY) + { + // we're waiting on a particular client, so IPC_WaitMessage must + // definitely fail with the NS_ERROR_xxx result. + + data->senderDead = PR_TRUE; + return IPC_DISCARD_MESSAGE; // consume the message + } + else + { + // otherwise inform the observer about the client death using a special + // null message with an empty target id, and fail IPC_WaitMessage call + // with NS_ERROR_xxx only if the observer accepts this message. + + ipcIMessageObserver *obs = data->observer; + if (!obs) + obs = td->observer; + NS_ASSERTION(obs, "must at least have a default observer"); + + nsresult rv = obs->OnMessageAvailable(status->ClientID(), nsID(), 0, 0); + if (rv != IPC_WAIT_NEXT_MESSAGE) + data->senderDead = PR_TRUE; + + return IPC_DISCARD_MESSAGE; // consume the message + } + } +#ifdef VBOX + else if ((data->senderID == IPC_SENDER_ANY || + status->ClientID() == data->senderID) && + status->ClientState() == IPCM_CLIENT_STATE_UP) + { + LOG(("sender (%d) we're waiting a message from (%d) has come up\n", + status->ClientID(), data->senderID)); + if (data->senderID == IPC_SENDER_ANY) + { + // inform the observer about the client appearance using a special + // null message with an empty target id, but a length of 1. + + ipcIMessageObserver *obs = data->observer; + if (!obs) + obs = td->observer; + NS_ASSERTION(obs, "must at least have a default observer"); + + nsresult rv = obs->OnMessageAvailable(status->ClientID(), nsID(), 0, 1); + /* VBoxSVC/VBoxXPCOMIPCD auto-start can cause that a client up + * message arrives while we're already waiting for a response + * from this client. Don't declare the connection as dead in + * this case. A client ID wraparound can't falsely trigger + * this, since the waiting thread would have hit the liveness + * check in the mean time. We MUST consume the message, otherwise + * IPCM messages pile up as long as there is a pending call, which + * can lead to severe processing overhead. */ + return IPC_DISCARD_MESSAGE; // consume the message + } + } +#endif /* VBOX */ + break; + } + default: + NS_NOTREACHED("unexpected message"); + } + return IPC_WAIT_NEXT_MESSAGE; // continue iterating + } + + nsresult rv = IPC_WAIT_NEXT_MESSAGE; + + if (data->senderID == IPC_SENDER_ANY || + msg->mMetaData == data->senderID) + { + ipcIMessageObserver *obs = data->observer; + if (!obs) + obs = td->observer; + NS_ASSERTION(obs, "must at least have a default observer"); + + rv = obs->OnMessageAvailable(msg->mMetaData, + msg->Target(), + (const PRUint8 *) msg->Data(), + msg->DataLen()); + } + + // stop iterating if we got a match that the observer accepted. + return rv != IPC_WAIT_NEXT_MESSAGE ? NS_OK : IPC_WAIT_NEXT_MESSAGE; +} + +nsresult +IPC_WaitMessage(PRUint32 aSenderID, + const nsID &aTarget, + ipcIMessageObserver *aObserver, + ipcIMessageObserver *aConsumer, + PRIntervalTime aTimeout) +{ + NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED); + + // do not permit waiting for IPCM messages + if (aTarget.Equals(IPCM_TARGET)) + return NS_ERROR_INVALID_ARG; + + // use aObserver as the message selector + WaitMessageSelectorData data = { aSenderID, aObserver, PR_FALSE }; + + ipcMessage *msg; + nsresult rv = WaitTarget(aTarget, aTimeout, &msg, WaitMessageSelector, &data); + if (NS_FAILED(rv)) + return rv; + + // if the selector has accepted some message, then we pass it to aConsumer + // for safe processing. The IPC susbsystem is quite stable here (i.e. we're + // not inside any of the monitors, and the message has been already removed + // from the pending queue). + if (aObserver && aConsumer) + { + aConsumer->OnMessageAvailable(msg->mMetaData, + msg->Target(), + (const PRUint8 *) msg->Data(), + msg->DataLen()); + } + + delete msg; + + // if the requested sender has died while waiting, return an error + if (data.senderDead) + return NS_ERROR_ABORT; // XXX better error code? + + return NS_OK; +} + +/* ------------------------------------------------------------------------- */ + +nsresult +IPC_GetID(PRUint32 *aClientID) +{ + NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED); + + *aClientID = gClientState->selfID; + return NS_OK; +} + +nsresult +IPC_AddName(const char *aName) +{ + NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED); + + return MakeIPCMRequest(new ipcmMessageClientAddName(aName)); +} + +nsresult +IPC_RemoveName(const char *aName) +{ + NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED); + + return MakeIPCMRequest(new ipcmMessageClientDelName(aName)); +} + +/* ------------------------------------------------------------------------- */ + +nsresult +IPC_AddClientObserver(ipcIClientObserver *aObserver) +{ + NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED); + + return gClientState->clientObservers.AppendObject(aObserver) + ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult +IPC_RemoveClientObserver(ipcIClientObserver *aObserver) +{ + NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED); + + for (PRInt32 i = 0; i < gClientState->clientObservers.Count(); ++i) + { + if (gClientState->clientObservers[i] == aObserver) + gClientState->clientObservers.RemoveObjectAt(i); + } + + return NS_OK; +} + +/* ------------------------------------------------------------------------- */ + +// this function could be called on any thread +nsresult +IPC_ResolveClientName(const char *aName, PRUint32 *aClientID) +{ + NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED); + + ipcMessage *msg = NULL; + + nsresult rv = MakeIPCMRequest(new ipcmMessageQueryClientByName(aName), &msg); + if (NS_FAILED(rv)) + { +#ifdef VBOX /* MakeIPCMRequest may return a failure (e.g. NS_ERROR_CALL_FAILED) and a response msg. */ + if (msg) + delete msg; +#endif + return rv; + } + + if (IPCM_GetType(msg) == IPCM_MSG_ACK_CLIENT_ID) + *aClientID = ipcMessageCast<ipcmMessageClientID>(msg)->ClientID(); + else + { + LOG(("unexpected IPCM response: type=%x\n", IPCM_GetType(msg))); + rv = NS_ERROR_UNEXPECTED; + } + + delete msg; + return rv; +} + +/* ------------------------------------------------------------------------- */ + +nsresult +IPC_ClientExists(PRUint32 aClientID, PRBool *aResult) +{ + // this is a bit of a hack. we forward a PING to the specified client. + // the assumption is that the forwarding will only succeed if the client + // exists, so we wait for the RESULT message corresponding to the FORWARD + // request. if that gives a successful status, then we know that the + // client exists. + + ipcmMessagePing ping; + + return MakeIPCMRequest(new ipcmMessageForward(IPCM_MSG_REQ_FORWARD, + aClientID, + IPCM_TARGET, + ping.Data(), + ping.DataLen())); +} + +/* ------------------------------------------------------------------------- */ + +nsresult +IPC_SpawnDaemon(const char *path) +{ + PRFileDesc *readable = nsnull, *writable = nsnull; + PRProcessAttr *attr = nsnull; + nsresult rv = NS_ERROR_FAILURE; + PRFileDesc *devNull; + char *const argv[] = { (char *const) path, nsnull }; + char c; + + // setup an anonymous pipe that we can use to determine when the daemon + // process has started up. the daemon will write a char to the pipe, and + // when we read it, we'll know to proceed with trying to connect to the + // daemon. + + if (PR_CreatePipe(&readable, &writable) != PR_SUCCESS) + goto end; + PR_SetFDInheritable(writable, PR_TRUE); + + attr = PR_NewProcessAttr(); + if (!attr) + goto end; + + if (PR_ProcessAttrSetInheritableFD(attr, writable, IPC_STARTUP_PIPE_NAME) != PR_SUCCESS) + goto end; + + devNull = PR_Open("/dev/null", PR_RDWR, 0); + if (!devNull) + goto end; + + PR_ProcessAttrSetStdioRedirect(attr, PR_StandardInput, devNull); + PR_ProcessAttrSetStdioRedirect(attr, PR_StandardOutput, devNull); + PR_ProcessAttrSetStdioRedirect(attr, PR_StandardError, devNull); + + if (PR_CreateProcessDetached(path, argv, nsnull, attr) != PR_SUCCESS) + goto end; + + // Close /dev/null + PR_Close(devNull); + // close the child end of the pipe in order to get notification on unexpected + // child termination instead of being infinitely blocked in PR_Read(). + PR_Close(writable); + writable = nsnull; + + if ((PR_Read(readable, &c, 1) != 1) || (c != IPC_STARTUP_PIPE_MAGIC)) + goto end; + + rv = NS_OK; +end: + if (readable) + PR_Close(readable); + if (writable) + PR_Close(writable); + if (attr) + PR_DestroyProcessAttr(attr); + return rv; +} + +/* ------------------------------------------------------------------------- */ + +PR_STATIC_CALLBACK(PLDHashOperator) +EnumerateTargetMapAndNotify(const nsID &aKey, + ipcTargetData *aData, + void *aClosure) +{ + nsAutoMonitor mon(aData->monitor); + + // wake up anyone waiting on this target. + mon.NotifyAll(); + + return PL_DHASH_NEXT; +} + +// called on a background thread +void +IPC_OnConnectionEnd(nsresult error) +{ + // now, go through the target map, and tickle each monitor. that should + // unblock any calls to WaitTarget. + +#ifndef VBOX_WITH_IPCCLIENT_RW_CS + nsAutoMonitor mon(gClientState->monitor); +#else + RTCritSectRwEnterExcl(&gClientState->critSect); +#endif + + gClientState->connected = PR_FALSE; + gClientState->targetMap.EnumerateRead(EnumerateTargetMapAndNotify, nsnull); + +#ifdef VBOX_WITH_IPCCLIENT_RW_CS + RTCritSectRwLeaveExcl(&gClientState->critSect); +#endif +} + +/* ------------------------------------------------------------------------- */ + +static void +PlaceOnPendingQ(const nsID &target, ipcTargetData *td, ipcMessage *msg) +{ + nsAutoMonitor mon(td->monitor); + + // we only want to dispatch a 'ProcessPendingQ' event if we have not + // already done so. + PRBool dispatchEvent = td->pendingQ.IsEmpty(); + + // put this message on our pending queue + td->pendingQ.Append(msg); + +#ifdef IPC_LOGGING + if (IPC_LOG_ENABLED()) + { + char *targetStr = target.ToString(); + LOG(("placed message on pending queue for target %s and notifying all...\n", targetStr)); + nsMemory::Free(targetStr); + } +#endif + + // wake up anyone waiting on this queue + mon.NotifyAll(); + + // proxy call to target's message procedure + if (dispatchEvent) + CallProcessPendingQ(target, td); +} + +PR_STATIC_CALLBACK(PLDHashOperator) +EnumerateTargetMapAndPlaceMsg(const nsID &aKey, + ipcTargetData *aData, + void *userArg) +{ + if (!aKey.Equals(IPCM_TARGET)) + { + // place a message clone to a target's event queue + ipcMessage *msg = (ipcMessage *) userArg; + PlaceOnPendingQ(aKey, aData, msg->Clone()); + } + + return PL_DHASH_NEXT; +} + +/* ------------------------------------------------------------------------- */ + +#ifdef IPC_LOGGING +#include "prprf.h" +#include <ctype.h> +#endif + +// called on a background thread +void +IPC_OnMessageAvailable(ipcMessage *msg) +{ +#ifdef IPC_LOGGING + if (IPC_LOG_ENABLED()) + { + char *targetStr = msg->Target().ToString(); + LOG(("got message for target: %s\n", targetStr)); + nsMemory::Free(targetStr); + +// IPC_LogBinary((const PRUint8 *) msg->Data(), msg->DataLen()); + } +#endif + + if (msg->Target().Equals(IPCM_TARGET)) + { + switch (IPCM_GetType(msg)) + { + // if this is a forwarded message, then post the inner message instead. + case IPCM_MSG_PSH_FORWARD: + { + ipcMessageCast<ipcmMessageForward> fwd(msg); + ipcMessage *innerMsg = new ipcMessage(fwd->InnerTarget(), + fwd->InnerData(), + fwd->InnerDataLen()); + // store the sender's client id in the meta-data field of the message. + innerMsg->mMetaData = fwd->ClientID(); + + delete msg; + + // recurse so we can handle forwarded IPCM messages + IPC_OnMessageAvailable(innerMsg); + return; + } + case IPCM_MSG_PSH_CLIENT_STATE: + { + ipcMessageCast<ipcmMessageClientState> status(msg); + PostEventToMainThread(new ipcEvent_ClientState(status->ClientID(), + status->ClientState())); + + // go through the target map, and place this message to every target's + // pending event queue. that unblocks all WaitTarget calls (on all + // targets) giving them an opportuninty to finish wait cycle because of + // the peer client death, when appropriate. +#ifndef VBOX_WITH_IPCCLIENT_RW_CS + nsAutoMonitor mon(gClientState->monitor); +#else + RTCritSectRwEnterShared(&gClientState->critSect); +#endif + + gClientState->targetMap.EnumerateRead(EnumerateTargetMapAndPlaceMsg, msg); + +#ifdef VBOX_WITH_IPCCLIENT_RW_CS + RTCritSectRwLeaveShared(&gClientState->critSect); +#endif + delete msg; + + return; + } + } + } + + nsRefPtr<ipcTargetData> td; + if (GetTarget(msg->Target(), getter_AddRefs(td))) + { + // make copy of target since |msg| may end up pointing to free'd memory + // once we notify the monitor inside PlaceOnPendingQ(). + const nsID target = msg->Target(); + + PlaceOnPendingQ(target, td, msg); + } + else + { + NS_WARNING("message target is undefined"); +#ifdef VBOX + delete msg; +#endif + } +} |