Back to index

lightning-sunbird  0.9+nobinonly
ipcConnectionUnix.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 IBM Corporation are Copyright (C) 2003
00019  * IBM Corporation. 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 "private/pprio.h"
00039 #include "prerror.h"
00040 #include "prthread.h"
00041 #include "prlock.h"
00042 #include "prlog.h" // for PR_ASSERT (we don't actually use NSPR logging)
00043 #include "prio.h"
00044 
00045 #include "ipcConnection.h"
00046 #include "ipcMessageQ.h"
00047 #include "ipcConfig.h"
00048 #include "ipcLog.h"
00049 
00050 
00051 //-----------------------------------------------------------------------------
00052 // NOTE: this code does not need to link with anything but NSPR.  that is by
00053 //       design, so it can be easily reused in other projects that want to 
00054 //       talk with mozilla's IPC daemon, but don't want to depend on xpcom.
00055 //       we depend at most on some xpcom header files, but no xpcom runtime
00056 //       symbols are used.
00057 //-----------------------------------------------------------------------------
00058 
00059 
00060 // single user systems, like OS/2, don't need these security checks.
00061 #ifndef XP_OS2
00062 #define IPC_SKIP_SECURITY_CHECKS
00063 #endif
00064 
00065 #ifndef IPC_SKIP_SECURITY_CHECKS
00066 #include <unistd.h>
00067 #include <sys/stat.h>
00068 #endif
00069 
00070 static PRStatus
00071 DoSecurityCheck(PRFileDesc *fd, const char *path)
00072 {
00073 #ifndef IPC_SKIP_SECURITY_CHECKS
00074     //
00075     // now that we have a connected socket; do some security checks on the
00076     // file descriptor.
00077     //
00078     //   (1) make sure owner matches
00079     //   (2) make sure permissions match expected permissions
00080     //
00081     // if these conditions aren't met then bail.
00082     //
00083     int unix_fd = PR_FileDesc2NativeHandle(fd);  
00084 
00085     struct stat st;
00086     if (fstat(unix_fd, &st) == -1) {
00087         LOG(("stat failed"));
00088         return PR_FAILURE;
00089     }
00090 
00091     if (st.st_uid != getuid() && st.st_uid != geteuid()) {
00092         //
00093         // on OSX 10.1.5, |fstat| has a bug when passed a file descriptor to
00094         // a socket.  it incorrectly returns a UID of 0.  however, |stat|
00095         // succeeds, but using |stat| introduces a race condition.
00096         //
00097         // XXX come up with a better security check.
00098         //
00099         if (st.st_uid != 0) {
00100             LOG(("userid check failed"));
00101             return PR_FAILURE;
00102         }
00103         if (stat(path, &st) == -1) {
00104             LOG(("stat failed"));
00105             return PR_FAILURE;
00106         }
00107         if (st.st_uid != getuid() && st.st_uid != geteuid()) {
00108             LOG(("userid check failed"));
00109             return PR_FAILURE;
00110         }
00111     }
00112 #endif
00113     return PR_SUCCESS;
00114 }
00115 
00116 //-----------------------------------------------------------------------------
00117 
00118 struct ipcCallback : public ipcListNode<ipcCallback>
00119 {
00120   ipcCallbackFunc  func;
00121   void            *arg;
00122 };
00123 
00124 typedef ipcList<ipcCallback> ipcCallbackQ;
00125 
00126 //-----------------------------------------------------------------------------
00127 
00128 struct ipcConnectionState
00129 {
00130   PRLock      *lock;
00131   PRPollDesc   fds[2];
00132   ipcCallbackQ callback_queue;
00133   ipcMessageQ  send_queue;
00134   PRUint32     send_offset; // amount of send_queue.First() already written.
00135   ipcMessage  *in_msg;
00136   PRBool       shutdown;
00137 };
00138 
00139 #define SOCK 0
00140 #define POLL 1
00141 
00142 static void
00143 ConnDestroy(ipcConnectionState *s)
00144 {
00145   if (s->lock)
00146     PR_DestroyLock(s->lock);  
00147 
00148   if (s->fds[SOCK].fd)
00149     PR_Close(s->fds[SOCK].fd);
00150 
00151   if (s->fds[POLL].fd)
00152     PR_DestroyPollableEvent(s->fds[POLL].fd);
00153 
00154   if (s->in_msg)
00155     delete s->in_msg;
00156 
00157   s->send_queue.DeleteAll();
00158   delete s;
00159 }
00160 
00161 static ipcConnectionState *
00162 ConnCreate(PRFileDesc *fd)
00163 {
00164   ipcConnectionState *s = new ipcConnectionState;
00165   if (!s)
00166     return NULL;
00167 
00168   s->lock = PR_NewLock();
00169   s->fds[SOCK].fd = NULL;
00170   s->fds[POLL].fd = PR_NewPollableEvent();
00171   s->send_offset = 0;
00172   s->in_msg = NULL;
00173   s->shutdown = PR_FALSE;
00174 
00175   if (!s->lock || !s->fds[1].fd)
00176   {
00177     ConnDestroy(s);
00178     return NULL;
00179   }
00180 
00181   // store this only if we are going to succeed.
00182   s->fds[SOCK].fd = fd;
00183 
00184   return s;
00185 }
00186 
00187 static nsresult
00188 ConnRead(ipcConnectionState *s)
00189 {
00190   char buf[1024];
00191   nsresult rv = NS_OK;
00192   PRInt32 n;
00193 
00194   do
00195   {
00196     n = PR_Read(s->fds[SOCK].fd, buf, sizeof(buf));
00197     if (n < 0)
00198     {
00199       PRErrorCode err = PR_GetError();
00200       if (err == PR_WOULD_BLOCK_ERROR)
00201       {
00202         // socket is empty... we need to go back to polling.
00203         break;
00204       }
00205       LOG(("PR_Read returned failure [err=%d]\n", err));
00206       rv = NS_ERROR_UNEXPECTED;
00207     }
00208     else if (n == 0)
00209     {
00210       LOG(("PR_Read returned EOF\n"));
00211       rv = NS_ERROR_UNEXPECTED;
00212     }
00213     else
00214     {
00215       const char *pdata = buf;
00216       while (n)
00217       {
00218         PRUint32 bytesRead;
00219         PRBool complete;
00220 
00221         if (!s->in_msg)
00222         {
00223           s->in_msg = new ipcMessage;
00224           if (!s->in_msg)
00225           {
00226             rv = NS_ERROR_OUT_OF_MEMORY;
00227             break;
00228           }
00229         }
00230 
00231         if (s->in_msg->ReadFrom(pdata, n, &bytesRead, &complete) != PR_SUCCESS)
00232         {
00233           LOG(("error reading IPC message\n"));
00234           rv = NS_ERROR_UNEXPECTED;
00235           break;
00236         }
00237 
00238         PR_ASSERT(PRUint32(n) >= bytesRead);
00239         n -= bytesRead;
00240         pdata += bytesRead;
00241 
00242         if (complete)
00243         {
00244           // protect against weird re-entrancy cases...
00245           ipcMessage *m = s->in_msg;
00246           s->in_msg = NULL;
00247 
00248           IPC_OnMessageAvailable(m);
00249         }
00250       }
00251     }
00252   }
00253   while (NS_SUCCEEDED(rv));
00254 
00255   return rv;
00256 }
00257 
00258 static nsresult
00259 ConnWrite(ipcConnectionState *s)
00260 {
00261   nsresult rv = NS_OK;
00262 
00263   PR_Lock(s->lock);
00264 
00265   // write one message and then return.
00266   if (s->send_queue.First())
00267   {
00268     PRInt32 n = PR_Write(s->fds[SOCK].fd,
00269                          s->send_queue.First()->MsgBuf() + s->send_offset,
00270                          s->send_queue.First()->MsgLen() - s->send_offset);
00271     if (n <= 0)
00272     {
00273       PRErrorCode err = PR_GetError();
00274       if (err == PR_WOULD_BLOCK_ERROR)
00275       {
00276         // socket is full... we need to go back to polling.
00277       }
00278       else
00279       {
00280         LOG(("error writing to socket [err=%d]\n", err));
00281         rv = NS_ERROR_UNEXPECTED;
00282       }
00283     }
00284     else
00285     {
00286       s->send_offset += n;
00287       if (s->send_offset == s->send_queue.First()->MsgLen())
00288       {
00289         s->send_queue.DeleteFirst();
00290         s->send_offset = 0;
00291 
00292         // if the send queue is empty, then we need to stop trying to write.
00293         if (s->send_queue.IsEmpty())
00294           s->fds[SOCK].in_flags &= ~PR_POLL_WRITE;
00295       }
00296     }
00297   }
00298 
00299   PR_Unlock(s->lock);
00300   return rv;
00301 }
00302 
00303 PR_STATIC_CALLBACK(void)
00304 ConnThread(void *arg)
00305 {
00306   PRInt32 num;
00307   nsresult rv = NS_OK;
00308 
00309   ipcConnectionState *s = (ipcConnectionState *) arg;
00310 
00311   // we monitor two file descriptors in this thread.  the first (at index 0) is
00312   // the socket connection with the IPC daemon.  the second (at index 1) is the
00313   // pollable event we monitor in order to know when to send messages to the
00314   // IPC daemon.
00315 
00316   s->fds[SOCK].in_flags = PR_POLL_READ;
00317   s->fds[POLL].in_flags = PR_POLL_READ;
00318 
00319   while (NS_SUCCEEDED(rv))
00320   {
00321     s->fds[SOCK].out_flags = 0;
00322     s->fds[POLL].out_flags = 0;
00323 
00324     //
00325     // poll on the IPC socket and NSPR pollable event
00326     //
00327     num = PR_Poll(s->fds, 2, PR_INTERVAL_NO_TIMEOUT);
00328     if (num > 0)
00329     {
00330       ipcCallbackQ cbs_to_run;
00331 
00332       // check if something has been added to the send queue.  if so, then
00333       // acknowledge pollable event (wait should not block), and configure
00334       // poll flags to find out when we can write.
00335 
00336       if (s->fds[POLL].out_flags & PR_POLL_READ)
00337       {
00338         PR_WaitForPollableEvent(s->fds[POLL].fd);
00339         PR_Lock(s->lock);
00340 
00341         if (!s->send_queue.IsEmpty())
00342           s->fds[SOCK].in_flags |= PR_POLL_WRITE;
00343 
00344         if (!s->callback_queue.IsEmpty())
00345           s->callback_queue.MoveTo(cbs_to_run);
00346 
00347         PR_Unlock(s->lock);
00348       }
00349 
00350       // check if we can read...
00351       if (s->fds[SOCK].out_flags & PR_POLL_READ)
00352         rv = ConnRead(s);
00353 
00354       // check if we can write...
00355       if (s->fds[SOCK].out_flags & PR_POLL_WRITE)
00356         rv = ConnWrite(s);
00357 
00358       // check if we have callbacks to run
00359       while (!cbs_to_run.IsEmpty())
00360       {
00361         ipcCallback *cb = cbs_to_run.First();
00362         (cb->func)(cb->arg);
00363         cbs_to_run.DeleteFirst();
00364       }
00365 
00366       // check if we should exit this thread.  delay processing a shutdown
00367       // request until after all queued up messages have been sent and until
00368       // after all queued up callbacks have been run.
00369       PR_Lock(s->lock);
00370       if (s->shutdown && s->send_queue.IsEmpty() && s->callback_queue.IsEmpty())
00371         rv = NS_ERROR_ABORT;
00372       PR_Unlock(s->lock);
00373     }
00374     else
00375     {
00376       LOG(("PR_Poll returned an error\n"));
00377       rv = NS_ERROR_UNEXPECTED;
00378     }
00379   }
00380 
00381   // notify termination of the IPC connection
00382   if (rv == NS_ERROR_ABORT)
00383     rv = NS_OK;
00384   IPC_OnConnectionEnd(rv);
00385 
00386   LOG(("IPC thread exiting\n"));
00387 }
00388 
00389 //-----------------------------------------------------------------------------
00390 // IPC connection API
00391 //-----------------------------------------------------------------------------
00392 
00393 static ipcConnectionState *gConnState = NULL;
00394 static PRThread *gConnThread = NULL;
00395 
00396 #ifdef DEBUG
00397 static PRThread *gMainThread = NULL;
00398 #endif
00399 
00400 nsresult
00401 TryConnect(PRFileDesc **result)
00402 {
00403   PRFileDesc *fd;
00404   PRNetAddr addr;
00405   PRSocketOptionData opt;
00406   nsresult rv = NS_ERROR_FAILURE;
00407 
00408   fd = PR_OpenTCPSocket(PR_AF_LOCAL);
00409   if (!fd)
00410     goto end;
00411 
00412   addr.local.family = PR_AF_LOCAL;
00413   IPC_GetDefaultSocketPath(addr.local.path, sizeof(addr.local.path));
00414 
00415   // blocking connect... will fail if no one is listening.
00416   if (PR_Connect(fd, &addr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE)
00417     goto end;
00418 
00419   // make socket non-blocking
00420   opt.option = PR_SockOpt_Nonblocking;
00421   opt.value.non_blocking = PR_TRUE;
00422   PR_SetSocketOption(fd, &opt);
00423 
00424   // do some security checks on connection socket...
00425   if (DoSecurityCheck(fd, addr.local.path) != PR_SUCCESS)
00426     goto end;
00427   
00428   *result = fd;
00429   return NS_OK;
00430 
00431 end:
00432   if (fd)
00433     PR_Close(fd);
00434 
00435   return rv;
00436 }
00437 
00438 nsresult
00439 IPC_Connect(const char *daemonPath)
00440 {
00441   // synchronous connect, spawn daemon if necessary.
00442 
00443   PRFileDesc *fd;
00444   nsresult rv = NS_ERROR_FAILURE;
00445 
00446   if (gConnState)
00447     return NS_ERROR_ALREADY_INITIALIZED;
00448 
00449   //
00450   // here's the connection algorithm:  try to connect to an existing daemon.
00451   // if the connection fails, then spawn the daemon (wait for it to be ready),
00452   // and then retry the connection.  it is critical that the socket used to
00453   // connect to the daemon not be inherited (this causes problems on RH9 at
00454   // least).
00455   //
00456 
00457   rv = TryConnect(&fd);
00458   if (NS_FAILED(rv))
00459   {
00460     rv = IPC_SpawnDaemon(daemonPath);
00461     if (NS_SUCCEEDED(rv))
00462       rv = TryConnect(&fd);
00463   }
00464 
00465   if (NS_FAILED(rv))
00466     goto end;
00467 
00468   //
00469   // ok, we have a connection to the daemon!
00470   //
00471 
00472   // build connection state object
00473   gConnState = ConnCreate(fd);
00474   if (!gConnState)
00475   {
00476     rv = NS_ERROR_OUT_OF_MEMORY;
00477     goto end;
00478   }
00479   fd = NULL; // connection state now owns the socket
00480 
00481   gConnThread = PR_CreateThread(PR_USER_THREAD,
00482                                 ConnThread,
00483                                 gConnState,
00484                                 PR_PRIORITY_NORMAL,
00485                                 PR_GLOBAL_THREAD,
00486                                 PR_JOINABLE_THREAD,
00487                                 0);
00488   if (!gConnThread)
00489   {
00490     rv = NS_ERROR_OUT_OF_MEMORY;
00491     goto end;
00492   }
00493 
00494 #ifdef DEBUG
00495   gMainThread = PR_GetCurrentThread();
00496 #endif
00497   return NS_OK;
00498 
00499 end:
00500   if (gConnState)
00501   {
00502     ConnDestroy(gConnState);
00503     gConnState = NULL;
00504   }
00505   if (fd)
00506     PR_Close(fd);
00507   return rv;
00508 }
00509 
00510 nsresult
00511 IPC_Disconnect()
00512 {
00513   // Must disconnect on same thread used to connect!
00514   PR_ASSERT(gMainThread == PR_GetCurrentThread());
00515 
00516   if (!gConnState || !gConnThread)
00517     return NS_ERROR_NOT_INITIALIZED;
00518 
00519   PR_Lock(gConnState->lock);
00520   gConnState->shutdown = PR_TRUE;
00521   PR_SetPollableEvent(gConnState->fds[POLL].fd);
00522   PR_Unlock(gConnState->lock);
00523 
00524   PR_JoinThread(gConnThread);
00525 
00526   ConnDestroy(gConnState);
00527 
00528   gConnState = NULL;
00529   gConnThread = NULL;
00530   return NS_OK;
00531 }
00532 
00533 nsresult
00534 IPC_SendMsg(ipcMessage *msg)
00535 {
00536   if (!gConnState || !gConnThread)
00537     return NS_ERROR_NOT_INITIALIZED;
00538 
00539   PR_Lock(gConnState->lock);
00540   gConnState->send_queue.Append(msg);
00541   PR_SetPollableEvent(gConnState->fds[POLL].fd);
00542   PR_Unlock(gConnState->lock);
00543 
00544   return NS_OK;
00545 }
00546 
00547 nsresult
00548 IPC_DoCallback(ipcCallbackFunc func, void *arg)
00549 {
00550   if (!gConnState || !gConnThread)
00551     return NS_ERROR_NOT_INITIALIZED;
00552   
00553   ipcCallback *callback = new ipcCallback;
00554   if (!callback)
00555     return NS_ERROR_OUT_OF_MEMORY;
00556   callback->func = func;
00557   callback->arg = arg;
00558 
00559   PR_Lock(gConnState->lock);
00560   gConnState->callback_queue.Append(callback);
00561   PR_SetPollableEvent(gConnState->fds[POLL].fd);
00562   PR_Unlock(gConnState->lock);
00563   return NS_OK;
00564 }
00565 
00566 //-----------------------------------------------------------------------------
00567 
00568 #ifdef TEST_STANDALONE
00569 
00570 void IPC_OnConnectionFault(nsresult rv)
00571 {
00572   LOG(("IPC_OnConnectionFault [rv=%x]\n", rv));
00573 }
00574 
00575 void IPC_OnMessageAvailable(ipcMessage *msg)
00576 {
00577   LOG(("IPC_OnMessageAvailable\n"));
00578   delete msg;
00579 }
00580 
00581 int main()
00582 {
00583   IPC_InitLog(">>>");
00584   IPC_Connect("/builds/moz-trunk/seamonkey-debug-build/dist/bin/mozilla-ipcd");
00585   IPC_Disconnect();
00586   return 0;
00587 }
00588 
00589 #endif