Back to index

lightning-sunbird  0.9+nobinonly
ipcdclient.cpp
Go to the documentation of this file.
00001 /* vim:set ts=2 sw=2 et cindent: */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla IPC.
00016  *
00017  * The Initial Developer of the Original Code is IBM Corporation.
00018  * Portions created by the Initial Developer are Copyright (C) 2004
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *   Darin Fisher <darin@meer.net>
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either the GNU General Public License Version 2 or later (the "GPL"), or
00026  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "ipcdclient.h"
00039 #include "ipcConnection.h"
00040 #include "ipcConfig.h"
00041 #include "ipcMessageQ.h"
00042 #include "ipcMessageUtils.h"
00043 #include "ipcLog.h"
00044 #include "ipcm.h"
00045 
00046 #include "nsIFile.h"
00047 #include "nsEventQueueUtils.h"
00048 #include "nsDirectoryServiceUtils.h"
00049 #include "nsDirectoryServiceDefs.h"
00050 #include "nsCOMPtr.h"
00051 #include "nsHashKeys.h"
00052 #include "nsRefPtrHashtable.h"
00053 #include "nsAutoLock.h"
00054 #include "nsProxyRelease.h"
00055 #include "nsCOMArray.h"
00056 
00057 #include "prio.h"
00058 #include "prproces.h"
00059 #include "pratom.h"
00060 
00061 /* ------------------------------------------------------------------------- */
00062 
00063 #define IPC_REQUEST_TIMEOUT PR_SecondsToInterval(30)
00064 
00065 /* ------------------------------------------------------------------------- */
00066 
00067 class ipcTargetData
00068 {
00069 public:
00070   static NS_HIDDEN_(ipcTargetData*) Create();
00071 
00072   // threadsafe addref/release
00073   NS_HIDDEN_(nsrefcnt) AddRef()  { return PR_AtomicIncrement(&refcnt); }
00074   NS_HIDDEN_(nsrefcnt) Release() { PRInt32 r = PR_AtomicDecrement(&refcnt); if (r == 0) delete this; return r; }
00075 
00076   NS_HIDDEN_(void) SetObserver(ipcIMessageObserver *aObserver, PRBool aOnCurrentThread);
00077 
00078   // protects access to the members of this class
00079   PRMonitor *monitor;
00080 
00081   // this may be null
00082   nsCOMPtr<ipcIMessageObserver> observer;
00083 
00084   // the message observer is called via this event queue
00085   nsCOMPtr<nsIEventQueue> eventQ;
00086   
00087   // incoming messages are added to this list
00088   ipcMessageQ pendingQ;
00089 
00090   // non-zero if the observer has been disabled (this means that new messages
00091   // should not be dispatched to the observer until the observer is re-enabled
00092   // via IPC_EnableMessageObserver).
00093   PRInt32 observerDisabled;
00094 
00095 private:
00096 
00097   ipcTargetData()
00098     : monitor(PR_NewMonitor())
00099     , observerDisabled(0)
00100     , refcnt(0)
00101     {}
00102 
00103   ~ipcTargetData()
00104   {
00105     if (monitor)
00106       PR_DestroyMonitor(monitor);
00107   }
00108 
00109   PRInt32 refcnt;
00110 };
00111 
00112 ipcTargetData *
00113 ipcTargetData::Create()
00114 {
00115   ipcTargetData *td = new ipcTargetData;
00116   if (!td)
00117     return NULL;
00118 
00119   if (!td->monitor)
00120   {
00121     delete td;
00122     return NULL;
00123   }
00124   return td;
00125 }
00126 
00127 void
00128 ipcTargetData::SetObserver(ipcIMessageObserver *aObserver, PRBool aOnCurrentThread)
00129 {
00130   observer = aObserver;
00131 
00132   if (aOnCurrentThread)
00133     NS_GetCurrentEventQ(getter_AddRefs(eventQ));
00134   else
00135     eventQ = nsnull;
00136 }
00137 
00138 /* ------------------------------------------------------------------------- */
00139 
00140 typedef nsRefPtrHashtable<nsIDHashKey, ipcTargetData> ipcTargetMap; 
00141 
00142 class ipcClientState
00143 {
00144 public:
00145   static NS_HIDDEN_(ipcClientState *) Create();
00146 
00147   ~ipcClientState()
00148   {
00149     if (monitor)
00150       PR_DestroyMonitor(monitor);
00151   }
00152 
00153   //
00154   // the monitor protects the targetMap and the connected flag.  
00155   //
00156   // NOTE: we use a PRMonitor for this instead of a PRLock because we need
00157   //       the lock to be re-entrant.  since we don't ever need to wait on
00158   //       this monitor, it might be worth it to implement a re-entrant
00159   //       wrapper for PRLock.
00160   //
00161   PRMonitor    *monitor;
00162   ipcTargetMap  targetMap;
00163   PRBool        connected;
00164 
00165   // our process's client id
00166   PRUint32      selfID; 
00167 
00168   nsCOMArray<ipcIClientObserver> clientObservers;
00169 
00170 private:
00171 
00172   ipcClientState()
00173     : monitor(PR_NewMonitor())
00174     , connected(PR_FALSE)
00175     , selfID(0)
00176   {}
00177 };
00178 
00179 ipcClientState *
00180 ipcClientState::Create()
00181 {
00182   ipcClientState *cs = new ipcClientState;
00183   if (!cs)
00184     return NULL;
00185 
00186   if (!cs->monitor || !cs->targetMap.Init())
00187   {
00188     delete cs;
00189     return NULL;
00190   }
00191 
00192   return cs;
00193 }
00194 
00195 /* ------------------------------------------------------------------------- */
00196 
00197 static ipcClientState *gClientState;
00198 
00199 static PRBool
00200 GetTarget(const nsID &aTarget, ipcTargetData **td)
00201 {
00202   nsAutoMonitor mon(gClientState->monitor);
00203   return gClientState->targetMap.Get(nsIDHashKey(&aTarget).GetKey(), td);
00204 }
00205 
00206 static PRBool
00207 PutTarget(const nsID &aTarget, ipcTargetData *td)
00208 {
00209   nsAutoMonitor mon(gClientState->monitor);
00210   return gClientState->targetMap.Put(nsIDHashKey(&aTarget).GetKey(), td);
00211 }
00212 
00213 static void
00214 DelTarget(const nsID &aTarget)
00215 {
00216   nsAutoMonitor mon(gClientState->monitor);
00217   gClientState->targetMap.Remove(nsIDHashKey(&aTarget).GetKey());
00218 }
00219 
00220 /* ------------------------------------------------------------------------- */
00221 
00222 static nsresult
00223 GetDaemonPath(nsCString &dpath)
00224 {
00225   nsCOMPtr<nsIFile> file;
00226 
00227   nsresult rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
00228                                        getter_AddRefs(file));
00229   if (NS_SUCCEEDED(rv))
00230   {
00231     rv = file->AppendNative(NS_LITERAL_CSTRING(IPC_DAEMON_APP_NAME));
00232     if (NS_SUCCEEDED(rv))
00233       rv = file->GetNativePath(dpath);
00234   }
00235 
00236   return rv;
00237 }
00238 
00239 /* ------------------------------------------------------------------------- */
00240 
00241 static void
00242 ProcessPendingQ(const nsID &aTarget)
00243 {
00244   ipcMessageQ tempQ;
00245 
00246   nsRefPtr<ipcTargetData> td;
00247   if (GetTarget(aTarget, getter_AddRefs(td)))
00248   {
00249     nsAutoMonitor mon(td->monitor);
00250 
00251     // if the observer for this target has been temporarily disabled, then
00252     // we must not processing any pending messages at this time.
00253 
00254     if (!td->observerDisabled)
00255       td->pendingQ.MoveTo(tempQ);
00256   }
00257 
00258   // process pending queue outside monitor
00259   while (!tempQ.IsEmpty())
00260   {
00261     ipcMessage *msg = tempQ.First();
00262 
00263     if (td->observer)
00264       td->observer->OnMessageAvailable(msg->mMetaData,
00265                                        msg->Target(),
00266                                        (const PRUint8 *) msg->Data(),
00267                                        msg->DataLen());
00268     else
00269     {
00270       // the IPCM target does not have an observer, and therefore any IPCM
00271       // messages that make it here will simply be dropped.
00272       NS_ASSERTION(aTarget.Equals(IPCM_TARGET), "unexpected target");
00273       LOG(("dropping IPCM message: type=%x\n", IPCM_GetType(msg)));
00274     }
00275     tempQ.DeleteFirst();
00276   }
00277 }
00278 
00279 /* ------------------------------------------------------------------------- */
00280 
00281 // WaitTarget enables support for multiple threads blocking on the same
00282 // message target.  the selector is called while inside the target's monitor.
00283 
00284 typedef PRBool (* ipcMessageSelector)(
00285   void *arg,
00286   ipcTargetData *td,
00287   const ipcMessage *msg
00288 );
00289 
00290 // selects any
00291 static PRBool
00292 DefaultSelector(void *arg, ipcTargetData *td, const ipcMessage *msg)
00293 {
00294   return PR_TRUE;
00295 }
00296 
00297 static nsresult
00298 WaitTarget(const nsID           &aTarget,
00299            PRIntervalTime        aTimeout,
00300            ipcMessage          **aMsg,
00301            ipcMessageSelector    aSelector = nsnull,
00302            void                 *aArg = nsnull)
00303 {
00304   *aMsg = nsnull;
00305 
00306   if (!aSelector)
00307     aSelector = DefaultSelector;
00308 
00309   nsRefPtr<ipcTargetData> td;
00310   if (!GetTarget(aTarget, getter_AddRefs(td)))
00311     return NS_ERROR_INVALID_ARG; // bad aTarget
00312 
00313   PRIntervalTime timeStart = PR_IntervalNow();
00314   PRIntervalTime timeEnd;
00315   if (aTimeout == PR_INTERVAL_NO_TIMEOUT)
00316     timeEnd = aTimeout;
00317   else if (aTimeout == PR_INTERVAL_NO_WAIT)
00318     timeEnd = timeStart;
00319   else
00320   {
00321     timeEnd = timeStart + aTimeout;
00322 
00323     // if overflowed, then set to max value
00324     if (timeEnd < timeStart)
00325       timeEnd = PR_INTERVAL_NO_TIMEOUT;
00326   }
00327 
00328   ipcMessage *lastChecked = nsnull, *beforeLastChecked = nsnull;
00329   nsresult rv = NS_ERROR_FAILURE;
00330 
00331   nsAutoMonitor mon(td->monitor);
00332 
00333   while (gClientState->connected)
00334   {
00335     NS_ASSERTION(!lastChecked, "oops");
00336 
00337     //
00338     // NOTE:
00339     //
00340     // we must start at the top of the pending queue, possibly revisiting
00341     // messages that our selector has already rejected.  this is necessary
00342     // because the queue may have been modified while we were waiting on
00343     // the monitor.  the impact of this on performance remains to be seen.
00344     //
00345     // one cheap solution is to keep a counter that is incremented each
00346     // time a message is removed from the pending queue.  that way we can
00347     // avoid revisiting all messages sometimes.
00348     //
00349 
00350     lastChecked = td->pendingQ.First();
00351     beforeLastChecked = nsnull;
00352 
00353     // loop over pending queue until we find a message that our selector likes.
00354     while (lastChecked)
00355     {
00356       if ((aSelector)(aArg, td, lastChecked))
00357       {
00358         // remove from pending queue
00359         if (beforeLastChecked)
00360           td->pendingQ.RemoveAfter(beforeLastChecked);
00361         else
00362           td->pendingQ.RemoveFirst();
00363         lastChecked->mNext = nsnull;
00364 
00365         *aMsg = lastChecked;
00366         break;
00367       }
00368 
00369       beforeLastChecked = lastChecked;
00370       lastChecked = lastChecked->mNext;
00371     }
00372       
00373     if (*aMsg)
00374     {
00375       rv = NS_OK;
00376       break;
00377     }
00378 
00379     // make sure we are still connected before waiting...
00380     if (!gClientState->connected)
00381     {
00382       rv = NS_ERROR_ABORT;
00383       break;
00384     }
00385 
00386     PRIntervalTime t = PR_IntervalNow();
00387     if (t > timeEnd) // check if timeout has expired
00388     {
00389       rv = IPC_ERROR_WOULD_BLOCK;
00390       break;
00391     }
00392     mon.Wait(timeEnd - t);
00393 
00394     LOG(("woke up from sleep [pendingQempty=%d connected=%d]\n",
00395           td->pendingQ.IsEmpty(), gClientState->connected));
00396   }
00397 
00398   return rv;
00399 }
00400 
00401 /* ------------------------------------------------------------------------- */
00402 
00403 static void
00404 PostEvent(nsIEventTarget *eventTarget, PLEvent *ev)
00405 {
00406   if (!ev)
00407     return;
00408 
00409   nsresult rv = eventTarget->PostEvent(ev);
00410   if (NS_FAILED(rv))
00411   {
00412     NS_WARNING("PostEvent failed");
00413     PL_DestroyEvent(ev);
00414   }
00415 }
00416 
00417 static void
00418 PostEventToMainThread(PLEvent *ev)
00419 {
00420   nsCOMPtr<nsIEventQueue> eventQ;
00421   NS_GetMainEventQ(getter_AddRefs(eventQ));
00422   if (!eventQ)
00423   {
00424     NS_WARNING("unable to get reference to main event queue");
00425     PL_DestroyEvent(ev);
00426     return;
00427   }
00428   PostEvent(eventQ, ev);
00429 }
00430 
00431 /* ------------------------------------------------------------------------- */
00432 
00433 class ipcEvent_ClientState : public PLEvent
00434 {
00435 public:
00436   ipcEvent_ClientState(PRUint32 aClientID, PRUint32 aClientState)
00437     : mClientID(aClientID)
00438     , mClientState(aClientState)
00439   {
00440     PL_InitEvent(this, nsnull, HandleEvent, DestroyEvent);
00441   }
00442 
00443   PR_STATIC_CALLBACK(void *) HandleEvent(PLEvent *ev)
00444   {
00445     // maybe we've been shutdown!
00446     if (!gClientState)
00447       return nsnull;
00448 
00449     ipcEvent_ClientState *self = (ipcEvent_ClientState *) ev;
00450 
00451     for (PRInt32 i=0; i<gClientState->clientObservers.Count(); ++i)
00452       gClientState->clientObservers[i]->OnClientStateChange(self->mClientID,
00453                                                             self->mClientState);
00454     return nsnull;
00455   }
00456 
00457   PR_STATIC_CALLBACK(void) DestroyEvent(PLEvent *ev)
00458   {
00459     delete (ipcEvent_ClientState *) ev;
00460   }
00461 
00462 private:
00463   PRUint32 mClientID;
00464   PRUint32 mClientState;
00465 };
00466 
00467 /* ------------------------------------------------------------------------- */
00468 
00469 class ipcEvent_ProcessPendingQ : public PLEvent
00470 {
00471 public:
00472   ipcEvent_ProcessPendingQ(const nsID &aTarget)
00473     : mTarget(aTarget)
00474   {
00475     PL_InitEvent(this, nsnull, HandleEvent, DestroyEvent);
00476   }
00477 
00478   PR_STATIC_CALLBACK(void *) HandleEvent(PLEvent *ev)
00479   {
00480     ProcessPendingQ(((ipcEvent_ProcessPendingQ *) ev)->mTarget);
00481     return nsnull;
00482   }
00483 
00484   PR_STATIC_CALLBACK(void) DestroyEvent(PLEvent *ev)
00485   {
00486     delete (ipcEvent_ProcessPendingQ *) ev;
00487   }
00488 
00489 private:
00490   const nsID mTarget;
00491 };
00492 
00493 static void
00494 CallProcessPendingQ(const nsID &target, ipcTargetData *td)
00495 {
00496   // we assume that we are inside td's monitor 
00497 
00498   PLEvent *ev = new ipcEvent_ProcessPendingQ(target);
00499   if (!ev)
00500     return;
00501 
00502   nsresult rv;
00503 
00504   if (td->eventQ)
00505     rv = td->eventQ->PostEvent(ev);
00506   else
00507     rv = IPC_DoCallback((ipcCallbackFunc) PL_HandleEvent, ev);
00508 
00509   if (NS_FAILED(rv))
00510     PL_DestroyEvent(ev);
00511 }
00512 
00513 /* ------------------------------------------------------------------------- */
00514 
00515 static void
00516 DisableMessageObserver(const nsID &aTarget)
00517 {
00518   nsRefPtr<ipcTargetData> td;
00519   if (GetTarget(aTarget, getter_AddRefs(td)))
00520   {
00521     nsAutoMonitor mon(td->monitor);
00522     ++td->observerDisabled;
00523   }
00524 }
00525 
00526 static void
00527 EnableMessageObserver(const nsID &aTarget)
00528 {
00529   nsRefPtr<ipcTargetData> td;
00530   if (GetTarget(aTarget, getter_AddRefs(td)))
00531   {
00532     nsAutoMonitor mon(td->monitor);
00533     if (td->observerDisabled > 0 && --td->observerDisabled == 0)
00534       if (!td->pendingQ.IsEmpty())
00535         CallProcessPendingQ(aTarget, td);
00536   }
00537 }
00538 
00539 /* ------------------------------------------------------------------------- */
00540 
00541 // selects the next IPCM message with matching request index
00542 static PRBool
00543 WaitIPCMResponseSelector(void *arg, ipcTargetData *td, const ipcMessage *msg)
00544 {
00545   PRUint32 requestIndex = *(PRUint32 *) arg;
00546   return IPCM_GetRequestIndex(msg) == requestIndex;
00547 }
00548 
00549 // wait for an IPCM response message.  if responseMsg is null, then it is
00550 // assumed that the caller does not care to get a reference to the 
00551 // response itself.  if the response is an IPCM_MSG_ACK_RESULT, then the
00552 // status code is mapped to a nsresult and returned by this function.
00553 static nsresult
00554 WaitIPCMResponse(PRUint32 requestIndex, ipcMessage **responseMsg = nsnull)
00555 {
00556   ipcMessage *msg;
00557 
00558   nsresult rv = WaitTarget(IPCM_TARGET, IPC_REQUEST_TIMEOUT, &msg,
00559                            WaitIPCMResponseSelector, &requestIndex);
00560   if (NS_FAILED(rv))
00561     return rv;
00562 
00563   if (IPCM_GetType(msg) == IPCM_MSG_ACK_RESULT)
00564   {
00565     ipcMessageCast<ipcmMessageResult> result(msg);
00566     if (result->Status() < 0)
00567       rv = NS_ERROR_FAILURE; // XXX nsresult_from_ipcm_result()
00568     else
00569       rv = NS_OK;
00570   }
00571 
00572   if (responseMsg)
00573     *responseMsg = msg;
00574   else
00575     delete msg;
00576 
00577   return rv;
00578 }
00579 
00580 // make an IPCM request and wait for a response.
00581 static nsresult
00582 MakeIPCMRequest(ipcMessage *msg, ipcMessage **responseMsg = nsnull)
00583 {
00584   if (!msg)
00585     return NS_ERROR_OUT_OF_MEMORY;
00586 
00587   PRUint32 requestIndex = IPCM_GetRequestIndex(msg);
00588 
00589   // suppress 'ProcessPendingQ' for IPCM messages until we receive the
00590   // response to this IPCM request.  if we did not do this then there
00591   // would be a race condition leading to the possible removal of our
00592   // response from the pendingQ between sending the request and waiting
00593   // for the response.
00594   DisableMessageObserver(IPCM_TARGET);
00595 
00596   nsresult rv = IPC_SendMsg(msg);
00597   if (NS_SUCCEEDED(rv))
00598     rv = WaitIPCMResponse(requestIndex, responseMsg);
00599 
00600   EnableMessageObserver(IPCM_TARGET);
00601   return rv;
00602 }
00603 
00604 /* ------------------------------------------------------------------------- */
00605 
00606 static void
00607 RemoveTarget(const nsID &aTarget, PRBool aNotifyDaemon)
00608 {
00609   DelTarget(aTarget);
00610 
00611   if (aNotifyDaemon)
00612   {
00613     nsresult rv = MakeIPCMRequest(new ipcmMessageClientDelTarget(aTarget));
00614     if (NS_FAILED(rv))
00615       LOG(("failed to delete target: rv=%x\n", rv));
00616   }
00617 }
00618 
00619 static nsresult
00620 DefineTarget(const nsID           &aTarget,
00621              ipcIMessageObserver  *aObserver,
00622              PRBool                aOnCurrentThread,
00623              PRBool                aNotifyDaemon,
00624              ipcTargetData       **aResult)
00625 {
00626   nsresult rv;
00627 
00628   nsRefPtr<ipcTargetData> td( ipcTargetData::Create() );
00629   if (!td)
00630     return NS_ERROR_OUT_OF_MEMORY;
00631   td->SetObserver(aObserver, aOnCurrentThread);
00632 
00633   if (!PutTarget(aTarget, td))
00634     return NS_ERROR_OUT_OF_MEMORY;
00635 
00636   if (aNotifyDaemon)
00637   {
00638     rv = MakeIPCMRequest(new ipcmMessageClientAddTarget(aTarget));
00639     if (NS_FAILED(rv))
00640     {
00641       LOG(("failed to add target: rv=%x\n", rv));
00642       RemoveTarget(aTarget, PR_FALSE);
00643       return rv;
00644     }
00645   }
00646 
00647   if (aResult)
00648     NS_ADDREF(*aResult = td);
00649   return NS_OK;
00650 }
00651 
00652 /* ------------------------------------------------------------------------- */
00653 
00654 static nsresult
00655 TryConnect()
00656 {
00657   nsCAutoString dpath;
00658   nsresult rv = GetDaemonPath(dpath);
00659   if (NS_FAILED(rv))
00660     return rv;
00661   
00662   rv = IPC_Connect(dpath.get());
00663   if (NS_FAILED(rv))
00664     return rv;
00665 
00666   gClientState->connected = PR_TRUE;
00667 
00668   rv = DefineTarget(IPCM_TARGET, nsnull, PR_FALSE, PR_FALSE, nsnull);
00669   if (NS_FAILED(rv))
00670     return rv;
00671 
00672   ipcMessage *msg;
00673 
00674   // send CLIENT_HELLO and wait for CLIENT_ID response...
00675   rv = MakeIPCMRequest(new ipcmMessageClientHello(), &msg);
00676   if (NS_FAILED(rv))
00677     return rv;
00678 
00679   if (IPCM_GetType(msg) == IPCM_MSG_ACK_CLIENT_ID)
00680     gClientState->selfID = ipcMessageCast<ipcmMessageClientID>(msg)->ClientID();
00681   else
00682   {
00683     LOG(("unexpected response from CLIENT_HELLO message: type=%x!\n",
00684         IPCM_GetType(msg)));
00685     rv = NS_ERROR_UNEXPECTED;
00686   }
00687 
00688   delete msg;
00689   return rv;
00690 }
00691 
00692 nsresult
00693 IPC_Init()
00694 {
00695   NS_ENSURE_TRUE(!gClientState, NS_ERROR_ALREADY_INITIALIZED);
00696 
00697   IPC_InitLog(">>>");
00698 
00699   gClientState = ipcClientState::Create();
00700   if (!gClientState)
00701     return NS_ERROR_OUT_OF_MEMORY;
00702 
00703   nsresult rv = TryConnect();
00704   if (NS_FAILED(rv))
00705     IPC_Shutdown();
00706 
00707   return rv;
00708 }
00709 
00710 nsresult
00711 IPC_Shutdown()
00712 {
00713   NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED);
00714 
00715   if (gClientState->connected)
00716     IPC_Disconnect();
00717 
00718   delete gClientState;
00719   gClientState = nsnull;
00720 
00721   return NS_OK;
00722 }
00723 
00724 /* ------------------------------------------------------------------------- */
00725 
00726 nsresult
00727 IPC_DefineTarget(const nsID          &aTarget,
00728                  ipcIMessageObserver *aObserver,
00729                  PRBool               aOnCurrentThread)
00730 {
00731   NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED);
00732 
00733   // do not permit the re-definition of the IPCM protocol's target.
00734   if (aTarget.Equals(IPCM_TARGET))
00735     return NS_ERROR_INVALID_ARG;
00736 
00737   nsresult rv;
00738 
00739   nsRefPtr<ipcTargetData> td;
00740   if (GetTarget(aTarget, getter_AddRefs(td)))
00741   {
00742     // clear out observer before removing target since we want to ensure that
00743     // the observer is released on the main thread.
00744     {
00745       nsAutoMonitor mon(td->monitor);
00746       td->SetObserver(aObserver, aOnCurrentThread);
00747     }
00748 
00749     // remove target outside of td's monitor to avoid holding the monitor
00750     // while entering the client state's monitor.
00751     if (!aObserver)
00752       RemoveTarget(aTarget, PR_TRUE);
00753 
00754     rv = NS_OK;
00755   }
00756   else
00757   {
00758     if (aObserver)
00759       rv = DefineTarget(aTarget, aObserver, aOnCurrentThread, PR_TRUE, nsnull);
00760     else
00761       rv = NS_ERROR_INVALID_ARG; // unknown target
00762   }
00763 
00764   return rv;
00765 }
00766 
00767 nsresult
00768 IPC_DisableMessageObserver(const nsID &aTarget)
00769 {
00770   NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED);
00771 
00772   // do not permit modifications to the IPCM protocol's target.
00773   if (aTarget.Equals(IPCM_TARGET))
00774     return NS_ERROR_INVALID_ARG;
00775 
00776   DisableMessageObserver(aTarget);
00777   return NS_OK;
00778 }
00779 
00780 nsresult
00781 IPC_EnableMessageObserver(const nsID &aTarget)
00782 {
00783   NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED);
00784 
00785   // do not permit modifications to the IPCM protocol's target.
00786   if (aTarget.Equals(IPCM_TARGET))
00787     return NS_ERROR_INVALID_ARG;
00788 
00789   EnableMessageObserver(aTarget);
00790   return NS_OK;
00791 }
00792 
00793 nsresult
00794 IPC_SendMessage(PRUint32       aReceiverID,
00795                 const nsID    &aTarget,
00796                 const PRUint8 *aData,
00797                 PRUint32       aDataLen)
00798 {
00799   NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED);
00800 
00801   // do not permit sending IPCM messages
00802   if (aTarget.Equals(IPCM_TARGET))
00803     return NS_ERROR_INVALID_ARG;
00804 
00805   nsresult rv;
00806   if (aReceiverID == 0)
00807   {
00808     ipcMessage *msg = new ipcMessage(aTarget, (const char *) aData, aDataLen);
00809     if (!msg)
00810       return NS_ERROR_OUT_OF_MEMORY;
00811 
00812     rv = IPC_SendMsg(msg);
00813   }
00814   else
00815     rv = MakeIPCMRequest(new ipcmMessageForward(IPCM_MSG_REQ_FORWARD,
00816                                                 aReceiverID,
00817                                                 aTarget,
00818                                                 (const char *) aData,
00819                                                 aDataLen));
00820 
00821   return rv;
00822 }
00823 
00824 struct WaitMessageSelectorData
00825 {
00826   PRUint32             senderID;
00827   ipcIMessageObserver *observer;
00828 };
00829 
00830 static PRBool WaitMessageSelector(void *arg, ipcTargetData *td, const ipcMessage *msg)
00831 {
00832   WaitMessageSelectorData *data = (WaitMessageSelectorData *) arg;
00833 
00834   nsresult rv = IPC_WAIT_NEXT_MESSAGE;
00835 
00836   if (msg->mMetaData == data->senderID)
00837   {
00838     ipcIMessageObserver *obs = data->observer;
00839     if (!obs)
00840       obs = td->observer;
00841     NS_ASSERTION(obs, "must at least have a default observer");
00842 
00843     rv = obs->OnMessageAvailable(msg->mMetaData,
00844                                  msg->Target(),
00845                                  (const PRUint8 *) msg->Data(),
00846                                  msg->DataLen());
00847   }
00848 
00849   // stop iterating if we got a match that the observer accepted.
00850   return rv != IPC_WAIT_NEXT_MESSAGE;
00851 }
00852 
00853 nsresult
00854 IPC_WaitMessage(PRUint32             aSenderID,
00855                 const nsID          &aTarget,
00856                 ipcIMessageObserver *aObserver,
00857                 PRIntervalTime       aTimeout)
00858 {
00859   NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED);
00860 
00861   // do not permit waiting for IPCM messages
00862   if (aTarget.Equals(IPCM_TARGET))
00863     return NS_ERROR_INVALID_ARG;
00864 
00865   WaitMessageSelectorData data = { aSenderID, aObserver };
00866 
00867   ipcMessage *msg;
00868   nsresult rv = WaitTarget(aTarget, aTimeout, &msg, WaitMessageSelector, &data);
00869   if (NS_FAILED(rv))
00870     return rv;
00871   delete msg;
00872 
00873   return NS_OK;
00874 }
00875 
00876 /* ------------------------------------------------------------------------- */
00877 
00878 nsresult
00879 IPC_GetID(PRUint32 *aClientID)
00880 {
00881   NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED);
00882 
00883   *aClientID = gClientState->selfID;
00884   return NS_OK;
00885 }
00886 
00887 nsresult
00888 IPC_AddName(const char *aName)
00889 {
00890   NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED);
00891 
00892   return MakeIPCMRequest(new ipcmMessageClientAddName(aName));
00893 }
00894 
00895 nsresult
00896 IPC_RemoveName(const char *aName)
00897 {
00898   NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED);
00899 
00900   return MakeIPCMRequest(new ipcmMessageClientDelName(aName));
00901 }
00902 
00903 /* ------------------------------------------------------------------------- */
00904 
00905 nsresult
00906 IPC_AddClientObserver(ipcIClientObserver *aObserver)
00907 {
00908   NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED);
00909 
00910   return gClientState->clientObservers.AppendObject(aObserver)
00911       ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
00912 }
00913 
00914 nsresult
00915 IPC_RemoveClientObserver(ipcIClientObserver *aObserver)
00916 {
00917   NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED);
00918 
00919   for (PRInt32 i = 0; i < gClientState->clientObservers.Count(); ++i)
00920   {
00921     if (gClientState->clientObservers[i] == aObserver)
00922       gClientState->clientObservers.RemoveObjectAt(i);
00923   }
00924 
00925   return NS_OK;
00926 }
00927 
00928 /* ------------------------------------------------------------------------- */
00929 
00930 // this function could be called on any thread
00931 nsresult
00932 IPC_ResolveClientName(const char *aName, PRUint32 *aClientID)
00933 {
00934   NS_ENSURE_TRUE(gClientState, NS_ERROR_NOT_INITIALIZED);
00935 
00936   ipcMessage *msg;
00937 
00938   nsresult rv = MakeIPCMRequest(new ipcmMessageQueryClientByName(aName), &msg);
00939   if (NS_FAILED(rv))
00940     return rv;
00941 
00942   if (IPCM_GetType(msg) == IPCM_MSG_ACK_CLIENT_ID)
00943     *aClientID = ipcMessageCast<ipcmMessageClientID>(msg)->ClientID();
00944   else
00945   {
00946     LOG(("unexpected IPCM response: type=%x\n", IPCM_GetType(msg)));
00947     rv = NS_ERROR_UNEXPECTED;
00948   }
00949     
00950   delete msg;
00951   return rv;
00952 }
00953 
00954 /* ------------------------------------------------------------------------- */
00955 
00956 nsresult
00957 IPC_ClientExists(PRUint32 aClientID, PRBool *aResult)
00958 {
00959   // this is a bit of a hack.  we forward a PING to the specified client.
00960   // the assumption is that the forwarding will only succeed if the client
00961   // exists, so we wait for the RESULT message corresponding to the FORWARD
00962   // request.  if that gives a successful status, then we know that the
00963   // client exists.
00964 
00965   ipcmMessagePing ping;
00966 
00967   return MakeIPCMRequest(new ipcmMessageForward(IPCM_MSG_REQ_FORWARD,
00968                                                 aClientID,
00969                                                 IPCM_TARGET,
00970                                                 ping.Data(),
00971                                                 ping.DataLen()));
00972 }
00973 
00974 /* ------------------------------------------------------------------------- */
00975 
00976 nsresult
00977 IPC_SpawnDaemon(const char *path)
00978 {
00979   PRFileDesc *readable = nsnull, *writable = nsnull;
00980   PRProcessAttr *attr = nsnull;
00981   nsresult rv = NS_ERROR_FAILURE;
00982   char *const argv[] = { (char *const) path, nsnull };
00983   char c;
00984 
00985   // setup an anonymous pipe that we can use to determine when the daemon
00986   // process has started up.  the daemon will write a char to the pipe, and
00987   // when we read it, we'll know to proceed with trying to connect to the
00988   // daemon.
00989 
00990   if (PR_CreatePipe(&readable, &writable) != PR_SUCCESS)
00991     goto end;
00992   PR_SetFDInheritable(writable, PR_TRUE);
00993 
00994   attr = PR_NewProcessAttr();
00995   if (!attr)
00996     goto end;
00997 
00998   if (PR_ProcessAttrSetInheritableFD(attr, writable, IPC_STARTUP_PIPE_NAME) != PR_SUCCESS)
00999     goto end;
01000 
01001   if (PR_CreateProcessDetached(path, argv, nsnull, attr) != PR_SUCCESS)
01002     goto end;
01003 
01004   if ((PR_Read(readable, &c, 1) != 1) && (c != IPC_STARTUP_PIPE_MAGIC))
01005     goto end;
01006 
01007   rv = NS_OK;
01008 end:
01009   if (readable)
01010     PR_Close(readable);
01011   if (writable)
01012     PR_Close(writable);
01013   if (attr)
01014     PR_DestroyProcessAttr(attr);
01015   return rv;
01016 }
01017 
01018 /* ------------------------------------------------------------------------- */
01019 
01020 PR_STATIC_CALLBACK(PLDHashOperator)
01021 EnumerateTargetMapAndNotify(const nsID    &aKey,
01022                             ipcTargetData *aData,
01023                             void          *aClosure)
01024 {
01025   nsAutoMonitor mon(aData->monitor);
01026 
01027   // wake up anyone waiting on this target.
01028   mon.NotifyAll();
01029 
01030   return PL_DHASH_NEXT;
01031 }
01032 
01033 // called on a background thread
01034 void
01035 IPC_OnConnectionEnd(nsresult error)
01036 {
01037   // now, go through the target map, and tickle each monitor.  that should
01038   // unblock any calls to WaitTarget.
01039 
01040   nsAutoMonitor mon(gClientState->monitor);
01041   gClientState->connected = PR_FALSE;
01042   gClientState->targetMap.EnumerateRead(EnumerateTargetMapAndNotify, nsnull);
01043 }
01044 
01045 /* ------------------------------------------------------------------------- */
01046 
01047 #ifdef IPC_LOGGING
01048 #include "prprf.h"
01049 #include <ctype.h>
01050 #endif
01051 
01052 // called on a background thread
01053 void
01054 IPC_OnMessageAvailable(ipcMessage *msg)
01055 {
01056 #ifdef IPC_LOGGING
01057   if (LOG_ENABLED())
01058   {
01059     char *targetStr = msg->Target().ToString();
01060     LOG(("got message for target: %s\n", targetStr));
01061     nsMemory::Free(targetStr);
01062 
01063     IPC_LogBinary((const PRUint8 *) msg->Data(), msg->DataLen());
01064   }
01065 #endif
01066 
01067   if (msg->Target().Equals(IPCM_TARGET))
01068   {
01069     switch (IPCM_GetType(msg))
01070     {
01071       // if this is a forwarded message, then post the inner message instead.
01072       case IPCM_MSG_PSH_FORWARD:
01073       {
01074         ipcMessageCast<ipcmMessageForward> fwd(msg);
01075         ipcMessage *innerMsg = new ipcMessage(fwd->InnerTarget(),
01076                                               fwd->InnerData(),
01077                                               fwd->InnerDataLen());
01078         // store the sender's client id in the meta-data field of the message.
01079         innerMsg->mMetaData = fwd->ClientID();
01080 
01081         delete msg;
01082 
01083         // recurse so we can handle forwarded IPCM messages
01084         IPC_OnMessageAvailable(innerMsg);
01085         return;
01086       }
01087       case IPCM_MSG_PSH_CLIENT_STATE:
01088       {
01089         ipcMessageCast<ipcmMessageClientState> status(msg);
01090         PostEventToMainThread(new ipcEvent_ClientState(status->ClientID(),
01091                                                        status->ClientState()));
01092         return;
01093       }
01094     }
01095   }
01096 
01097   nsRefPtr<ipcTargetData> td;
01098   if (GetTarget(msg->Target(), getter_AddRefs(td)))
01099   {
01100     nsAutoMonitor mon(td->monitor);
01101 
01102     // we only want to dispatch a 'ProcessPendingQ' event if we have not
01103     // already done so.
01104     PRBool dispatchEvent = td->pendingQ.IsEmpty();
01105 
01106     // put this message on our pending queue
01107     td->pendingQ.Append(msg);
01108 
01109     // make copy of target since |msg| may end up pointing to free'd memory
01110     // once we notify the monitor.
01111     const nsID target = msg->Target();
01112 
01113     LOG(("placed message on pending queue for target and notifying all...\n"));
01114 
01115     // wake up anyone waiting on this queue
01116     mon.NotifyAll();
01117 
01118     // proxy call to target's message procedure
01119     if (dispatchEvent)
01120       CallProcessPendingQ(target, td);
01121   }
01122   else
01123   {
01124     NS_WARNING("message target is undefined");
01125   }
01126 }