Back to index

lightning-sunbird  0.9+nobinonly
ipcdUnix.cpp
Go to the documentation of this file.
00001 /* ***** BEGIN LICENSE BLOCK *****
00002  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003  *
00004  * The contents of this file are subject to the Mozilla Public License Version
00005  * 1.1 (the "License"); you may not use this file except in compliance with
00006  * the License. You may obtain a copy of the License at
00007  * http://www.mozilla.org/MPL/
00008  *
00009  * Software distributed under the License is distributed on an "AS IS" basis,
00010  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011  * for the specific language governing rights and limitations under the
00012  * License.
00013  *
00014  * The Original Code is Mozilla IPC.
00015  *
00016  * The Initial Developer of the Original Code is
00017  * Netscape Communications Corporation.
00018  * Portions created by the Initial Developer are Copyright (C) 2002
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *   Darin Fisher <darin@netscape.com>
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 <sys/types.h>
00039 #include <sys/stat.h>
00040 #include <unistd.h>
00041 #include <fcntl.h>
00042 #include <signal.h>
00043 #include <stdio.h>
00044 #include <stdlib.h>
00045 #include <string.h>
00046 
00047 #include "prio.h"
00048 #include "prerror.h"
00049 #include "prthread.h"
00050 #include "prinrval.h"
00051 #include "plstr.h"
00052 #include "prprf.h"
00053 
00054 #include "ipcConfig.h"
00055 #include "ipcLog.h"
00056 #include "ipcMessage.h"
00057 #include "ipcClient.h"
00058 #include "ipcModuleReg.h"
00059 #include "ipcdPrivate.h"
00060 #include "ipcd.h"
00061 
00062 #if 0
00063 static void
00064 IPC_Sleep(int seconds)
00065 {
00066     while (seconds > 0) {
00067         LOG(("\rsleeping for %d seconds...", seconds));
00068         PR_Sleep(PR_SecondsToInterval(1));
00069         --seconds;
00070     }
00071     LOG(("\ndone sleeping\n"));
00072 }
00073 #endif
00074 
00075 //-----------------------------------------------------------------------------
00076 // ipc directory and locking...
00077 //-----------------------------------------------------------------------------
00078 
00079 //
00080 // advisory file locking is used to ensure that only one IPC daemon is active
00081 // and bound to the local domain socket at a time.
00082 //
00083 // XXX this code does not work on OS/2.
00084 //
00085 #if !defined(XP_OS2)
00086 #define IPC_USE_FILE_LOCK
00087 #endif
00088 
00089 #ifdef IPC_USE_FILE_LOCK
00090 
00091 static int ipcLockFD = 0;
00092 
00093 static PRBool AcquireDaemonLock(const char *baseDir)
00094 {
00095     const char lockName[] = "lock";
00096 
00097     int dirLen = strlen(baseDir);
00098     int len = dirLen            // baseDir
00099             + 1                 // "/"
00100             + sizeof(lockName); // "lock"
00101 
00102     char *lockFile = (char *) malloc(len);
00103     memcpy(lockFile, baseDir, dirLen);
00104     lockFile[dirLen] = '/';
00105     memcpy(lockFile + dirLen + 1, lockName, sizeof(lockName));
00106 
00107     // 
00108     // open lock file.  it remains open until we shutdown.
00109     //
00110     ipcLockFD = open(lockFile, O_WRONLY|O_CREAT, S_IWUSR|S_IRUSR);
00111 
00112     free(lockFile);
00113 
00114     if (ipcLockFD == -1)
00115         return PR_FALSE;
00116 
00117     //
00118     // we use fcntl for locking.  assumption: filesystem should be local.
00119     // this API is nice because the lock will be automatically released
00120     // when the process dies.  it will also be released when the file
00121     // descriptor is closed.
00122     //
00123     struct flock lock;
00124     lock.l_type = F_WRLCK;
00125     lock.l_start = 0;
00126     lock.l_len = 0;
00127     lock.l_whence = SEEK_SET;
00128     if (fcntl(ipcLockFD, F_SETLK, &lock) == -1)
00129         return PR_FALSE;
00130 
00131     //
00132     // truncate lock file once we have exclusive access to it.
00133     //
00134     ftruncate(ipcLockFD, 0);
00135 
00136     //
00137     // write our PID into the lock file (this just seems like a good idea...
00138     // no real purpose otherwise).
00139     //
00140     char buf[32];
00141     int nb = PR_snprintf(buf, sizeof(buf), "%u\n", (unsigned long) getpid());
00142     write(ipcLockFD, buf, nb);
00143 
00144     return PR_TRUE;
00145 }
00146 
00147 static PRBool InitDaemonDir(const char *socketPath)
00148 {
00149     LOG(("InitDaemonDir [sock=%s]\n", socketPath));
00150 
00151     char *baseDir = PL_strdup(socketPath);
00152 
00153     //
00154     // make sure IPC directory exists (XXX this should be recursive)
00155     //
00156     char *p = strrchr(baseDir, '/');
00157     if (p)
00158         p[0] = '\0';
00159     mkdir(baseDir, 0700);
00160 
00161     //
00162     // if we can't acquire the daemon lock, then another daemon
00163     // must be active, so bail.
00164     //
00165     PRBool haveLock = AcquireDaemonLock(baseDir);
00166 
00167     PL_strfree(baseDir);
00168 
00169     if (haveLock) {
00170         // delete an existing socket to prevent bind from failing.
00171         unlink(socketPath);
00172     }
00173     return haveLock;
00174 }
00175 
00176 static void ShutdownDaemonDir()
00177 {
00178     LOG(("ShutdownDaemonDir\n"));
00179 
00180     // deleting directory and files underneath it allows another process
00181     // to think it has exclusive access.  better to just leave the hidden
00182     // directory in /tmp and let the OS clean it up via the usual tmpdir
00183     // cleanup cron job.
00184 
00185     // this removes the advisory lock, allowing other processes to acquire it.
00186     if (ipcLockFD) {
00187         close(ipcLockFD);
00188         ipcLockFD = 0;
00189     }
00190 }
00191 
00192 #endif // IPC_USE_FILE_LOCK
00193 
00194 //-----------------------------------------------------------------------------
00195 // poll list
00196 //-----------------------------------------------------------------------------
00197 
00198 //
00199 // declared in ipcdPrivate.h
00200 //
00201 ipcClient *ipcClients = NULL;
00202 int        ipcClientCount = 0;
00203 
00204 //
00205 // the first element of this array is always zero; this is done so that the
00206 // k'th element of ipcClientArray corresponds to the k'th element of
00207 // ipcPollList.
00208 //
00209 static ipcClient ipcClientArray[IPC_MAX_CLIENTS + 1];
00210 
00211 //
00212 // element 0 contains the "server socket"
00213 //
00214 static PRPollDesc ipcPollList[IPC_MAX_CLIENTS + 1];
00215 
00216 //-----------------------------------------------------------------------------
00217 
00218 static int AddClient(PRFileDesc *fd)
00219 {
00220     if (ipcClientCount == IPC_MAX_CLIENTS) {
00221         LOG(("reached maximum client limit\n"));
00222         return -1;
00223     }
00224 
00225     int pollCount = ipcClientCount + 1;
00226 
00227     ipcClientArray[pollCount].Init();
00228 
00229     ipcPollList[pollCount].fd = fd;
00230     ipcPollList[pollCount].in_flags = PR_POLL_READ;
00231     ipcPollList[pollCount].out_flags = 0;
00232 
00233     ++ipcClientCount;
00234     return 0;
00235 }
00236 
00237 static int RemoveClient(int clientIndex)
00238 {
00239     PRPollDesc *pd = &ipcPollList[clientIndex];
00240 
00241     PR_Close(pd->fd);
00242 
00243     ipcClientArray[clientIndex].Finalize();
00244 
00245     //
00246     // keep the clients and poll_fds contiguous; move the last one into
00247     // the spot held by the one that is going away.
00248     //
00249     int toIndex = clientIndex;
00250     int fromIndex = ipcClientCount;
00251     if (fromIndex != toIndex) {
00252         memcpy(&ipcClientArray[toIndex], &ipcClientArray[fromIndex], sizeof(ipcClient));
00253         memcpy(&ipcPollList[toIndex], &ipcPollList[fromIndex], sizeof(PRPollDesc));
00254     }
00255 
00256     //
00257     // zero out the old entries.
00258     //
00259     memset(&ipcClientArray[fromIndex], 0, sizeof(ipcClient));
00260     memset(&ipcPollList[fromIndex], 0, sizeof(PRPollDesc));
00261 
00262     --ipcClientCount;
00263     return 0;
00264 }
00265 
00266 //-----------------------------------------------------------------------------
00267 
00268 static void PollLoop(PRFileDesc *listenFD)
00269 {
00270     // the first element of ipcClientArray is unused.
00271     memset(ipcClientArray, 0, sizeof(ipcClientArray));
00272     ipcClients = ipcClientArray + 1;
00273     ipcClientCount = 0;
00274 
00275     ipcPollList[0].fd = listenFD;
00276     ipcPollList[0].in_flags = PR_POLL_EXCEPT | PR_POLL_READ;
00277     
00278     while (1) {
00279         PRInt32 rv;
00280         PRIntn i;
00281 
00282         int pollCount = ipcClientCount + 1;
00283 
00284         ipcPollList[0].out_flags = 0;
00285 
00286         //
00287         // poll
00288         //
00289         // timeout after 5 minutes.  if no connections after timeout, then
00290         // exit.  this timeout ensures that we don't stay resident when no
00291         // clients are interested in connecting after spawning the daemon.
00292         //
00293         // XXX add #define for timeout value
00294         //
00295         LOG(("calling PR_Poll [pollCount=%d]\n", pollCount));
00296         rv = PR_Poll(ipcPollList, pollCount, PR_SecondsToInterval(60 * 5));
00297         if (rv == -1) {
00298             LOG(("PR_Poll failed [%d]\n", PR_GetError()));
00299             return;
00300         }
00301 
00302         if (rv > 0) {
00303             //
00304             // process clients that are ready
00305             //
00306             for (i = 1; i < pollCount; ++i) {
00307                 if (ipcPollList[i].out_flags != 0) {
00308                     ipcPollList[i].in_flags =
00309                         ipcClientArray[i].Process(ipcPollList[i].fd,
00310                                                   ipcPollList[i].out_flags);
00311                     ipcPollList[i].out_flags = 0;
00312                 }
00313             }
00314 
00315             //
00316             // cleanup any dead clients (indicated by a zero in_flags)
00317             //
00318             for (i = pollCount - 1; i >= 1; --i) {
00319                 if (ipcPollList[i].in_flags == 0)
00320                     RemoveClient(i);
00321             }
00322 
00323             //
00324             // check for new connection
00325             //
00326             if (ipcPollList[0].out_flags & PR_POLL_READ) {
00327                 LOG(("got new connection\n"));
00328 
00329                 PRNetAddr clientAddr;
00330                 memset(&clientAddr, 0, sizeof(clientAddr));
00331                 PRFileDesc *clientFD;
00332 
00333                 clientFD = PR_Accept(listenFD, &clientAddr, PR_INTERVAL_NO_WAIT);
00334                 if (clientFD == NULL) {
00335                     // ignore this error... perhaps the client disconnected.
00336                     LOG(("PR_Accept failed [%d]\n", PR_GetError()));
00337                 }
00338                 else {
00339                     // make socket non-blocking
00340                     PRSocketOptionData opt;
00341                     opt.option = PR_SockOpt_Nonblocking;
00342                     opt.value.non_blocking = PR_TRUE;
00343                     PR_SetSocketOption(clientFD, &opt);
00344 
00345                     if (AddClient(clientFD) != 0)
00346                         PR_Close(clientFD);
00347                 }
00348             }
00349         }
00350 
00351         //
00352         // shutdown if no clients
00353         //
00354         if (ipcClientCount == 0) {
00355             LOG(("shutting down\n"));
00356             break;
00357         }
00358     }
00359 }
00360 
00361 //-----------------------------------------------------------------------------
00362 
00363 PRStatus
00364 IPC_PlatformSendMsg(ipcClient  *client, ipcMessage *msg)
00365 {
00366     LOG(("IPC_PlatformSendMsg\n"));
00367 
00368     //
00369     // must copy message onto send queue.
00370     //
00371     client->EnqueueOutboundMsg(msg);
00372 
00373     //
00374     // since our Process method may have already been called, we must ensure
00375     // that the PR_POLL_WRITE flag is set.
00376     //
00377     int clientIndex = client - ipcClientArray;
00378     ipcPollList[clientIndex].in_flags |= PR_POLL_WRITE;
00379 
00380     return PR_SUCCESS;
00381 }
00382 
00383 //-----------------------------------------------------------------------------
00384 
00385 int main(int argc, char **argv)
00386 {
00387     PRFileDesc *listenFD = NULL;
00388     PRNetAddr addr;
00389 
00390     //
00391     // ignore SIGINT so <ctrl-c> from terminal only kills the client
00392     // which spawned this daemon.
00393     //
00394     signal(SIGINT, SIG_IGN);
00395     // XXX block others?  check cartman
00396 
00397     // ensure strict file permissions
00398     umask(0077);
00399 
00400     IPC_InitLog("###");
00401 
00402     LOG(("daemon started...\n"));
00403 
00404     //XXX uncomment these lines to test slow starting daemon
00405     //IPC_Sleep(2);
00406 
00407     // set socket address
00408     addr.local.family = PR_AF_LOCAL;
00409     if (argc < 2)
00410         IPC_GetDefaultSocketPath(addr.local.path, sizeof(addr.local.path));
00411     else
00412         PL_strncpyz(addr.local.path, argv[1], sizeof(addr.local.path));
00413 
00414 #ifdef IPC_USE_FILE_LOCK
00415     if (!InitDaemonDir(addr.local.path)) {
00416         LOG(("InitDaemonDir failed\n"));
00417         goto end;
00418     }
00419 #endif
00420 
00421     listenFD = PR_OpenTCPSocket(PR_AF_LOCAL);
00422     if (!listenFD) {
00423         LOG(("PR_OpenTCPSocket failed [%d]\n", PR_GetError()));
00424         goto end;
00425     }
00426 
00427     if (PR_Bind(listenFD, &addr) != PR_SUCCESS) {
00428         LOG(("PR_Bind failed [%d]\n", PR_GetError()));
00429         goto end;
00430     }
00431 
00432     IPC_InitModuleReg(argv[0]);
00433 
00434     if (PR_Listen(listenFD, 5) != PR_SUCCESS) {
00435         LOG(("PR_Listen failed [%d]\n", PR_GetError()));
00436         goto end;
00437     }
00438 
00439     IPC_NotifyParent();
00440 
00441     PollLoop(listenFD);
00442 
00443 end:
00444     IPC_ShutdownModuleReg();
00445 
00446     IPC_NotifyParent();
00447 
00448     //IPC_Sleep(5);
00449 
00450 #ifdef IPC_USE_FILE_LOCK
00451     // it is critical that we release the lock before closing the socket,
00452     // otherwise, a client might launch another daemon that would be unable
00453     // to acquire the lock and would then leave the client without a daemon.
00454  
00455     ShutdownDaemonDir();
00456 #endif
00457 
00458     if (listenFD) {
00459         LOG(("closing socket\n"));
00460         PR_Close(listenFD);
00461     }
00462     return 0;
00463 }