diff options
Diffstat (limited to 'src/libs/xpcom18a4/ipc/ipcd/daemon/src/ipcdUnix.cpp')
-rw-r--r-- | src/libs/xpcom18a4/ipc/ipcd/daemon/src/ipcdUnix.cpp | 600 |
1 files changed, 600 insertions, 0 deletions
diff --git a/src/libs/xpcom18a4/ipc/ipcd/daemon/src/ipcdUnix.cpp b/src/libs/xpcom18a4/ipc/ipcd/daemon/src/ipcdUnix.cpp new file mode 100644 index 00000000..026e9e52 --- /dev/null +++ b/src/libs/xpcom18a4/ipc/ipcd/daemon/src/ipcdUnix.cpp @@ -0,0 +1,600 @@ +/* ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher <darin@netscape.com> + * + * 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 <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if defined(VBOX) && !defined(XP_OS2) +# include <sys/resource.h> +# include <errno.h> +#endif + +#ifdef VBOX +# include <iprt/initterm.h> +#endif + +#include "prio.h" +#include "prerror.h" +#include "prthread.h" +#include "prinrval.h" +#include "plstr.h" +#include "prprf.h" + +#include "ipcConfig.h" +#include "ipcLog.h" +#include "ipcMessage.h" +#include "ipcClient.h" +#include "ipcModuleReg.h" +#include "ipcdPrivate.h" +#include "ipcd.h" + +#if 0 +static void +IPC_Sleep(int seconds) +{ + while (seconds > 0) { + LOG(("\rsleeping for %d seconds...", seconds)); + PR_Sleep(PR_SecondsToInterval(1)); + --seconds; + } + LOG(("\ndone sleeping\n")); +} +#endif + +//----------------------------------------------------------------------------- +// ipc directory and locking... +//----------------------------------------------------------------------------- + +// +// advisory file locking is used to ensure that only one IPC daemon is active +// and bound to the local domain socket at a time. +// +// XXX this code does not work on OS/2. +// +#if !defined(XP_OS2) +#define IPC_USE_FILE_LOCK +#endif + +#ifdef IPC_USE_FILE_LOCK + +enum Status +{ + EOk = 0, + ELockFileOpen = -1, + ELockFileLock = -2, + ELockFileOwner = -3, +}; + +static int ipcLockFD = 0; + +static Status AcquireDaemonLock(const char *baseDir) +{ + const char lockName[] = "lock"; + + int dirLen = strlen(baseDir); + int len = dirLen // baseDir + + 1 // "/" + + sizeof(lockName); // "lock" + + char *lockFile = (char *) malloc(len); + memcpy(lockFile, baseDir, dirLen); + lockFile[dirLen] = '/'; + memcpy(lockFile + dirLen + 1, lockName, sizeof(lockName)); + +#ifdef VBOX + // + // Security checks for the directory + // + struct stat st; + if (stat(baseDir, &st) == -1) + { + printf("Cannot stat '%s'.\n", baseDir); + return ELockFileOwner; + } + + if (st.st_uid != getuid() && st.st_uid != geteuid()) + { + printf("Wrong owner (%d) of '%s'", st.st_uid, baseDir); + if ( !stat("/tmp", &st) + && (st.st_mode & 07777) != 01777) + printf(" -- check /tmp permissions (%o should be 1777)\n", + st.st_mode & 07777); + printf(".\n"); + return ELockFileOwner; + } + + if (st.st_mode != (S_IRUSR | S_IWUSR | S_IXUSR | S_IFDIR)) + { + printf("Wrong mode (%o) of '%s'", st.st_mode, baseDir); + if ( !stat("/tmp", &st) + && (st.st_mode & 07777) != 01777) + printf(" -- check /tmp permissions (%o should be 1777)\n", + st.st_mode & 07777); + printf(".\n"); + return ELockFileOwner; + } +#endif + + // + // open lock file. it remains open until we shutdown. + // + ipcLockFD = open(lockFile, O_WRONLY|O_CREAT, S_IWUSR|S_IRUSR); + +#ifndef VBOX + free(lockFile); +#endif + + if (ipcLockFD == -1) + return ELockFileOpen; + +#ifdef VBOX + // + // Security checks for the lock file + // + if (fstat(ipcLockFD, &st) == -1) + { + printf("Cannot stat '%s'.\n", lockFile); + free(lockFile); + return ELockFileOwner; + } + + if (st.st_uid != getuid() && st.st_uid != geteuid()) + { + printf("Wrong owner (%d) of '%s'.\n", st.st_uid, lockFile); + free(lockFile); + return ELockFileOwner; + } + + if (st.st_mode != (S_IRUSR | S_IWUSR | S_IFREG)) + { + printf("Wrong mode (%o) of '%s'.\n", st.st_mode, lockFile); + free(lockFile); + return ELockFileOwner; + } + + free(lockFile); +#endif + + // + // we use fcntl for locking. assumption: filesystem should be local. + // this API is nice because the lock will be automatically released + // when the process dies. it will also be released when the file + // descriptor is closed. + // + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_start = 0; + lock.l_len = 0; + lock.l_whence = SEEK_SET; + if (fcntl(ipcLockFD, F_SETLK, &lock) == -1) + return ELockFileLock; + + // + // truncate lock file once we have exclusive access to it. + // + ftruncate(ipcLockFD, 0); + + // + // write our PID into the lock file (this just seems like a good idea... + // no real purpose otherwise). + // + char buf[32]; + int nb = PR_snprintf(buf, sizeof(buf), "%u\n", (unsigned long) getpid()); + write(ipcLockFD, buf, nb); + + return EOk; +} + +static Status InitDaemonDir(const char *socketPath) +{ + LOG(("InitDaemonDir [sock=%s]\n", socketPath)); + + char *baseDir = PL_strdup(socketPath); + + // + // make sure IPC directory exists (XXX this should be recursive) + // + char *p = strrchr(baseDir, '/'); + if (p) + p[0] = '\0'; + mkdir(baseDir, 0700); + + // + // if we can't acquire the daemon lock, then another daemon + // must be active, so bail. + // + Status status = AcquireDaemonLock(baseDir); + + PL_strfree(baseDir); + + if (status == EOk) { + // delete an existing socket to prevent bind from failing. + unlink(socketPath); + } + return status; +} + +static void ShutdownDaemonDir() +{ + LOG(("ShutdownDaemonDir\n")); + + // deleting directory and files underneath it allows another process + // to think it has exclusive access. better to just leave the hidden + // directory in /tmp and let the OS clean it up via the usual tmpdir + // cleanup cron job. + + // this removes the advisory lock, allowing other processes to acquire it. + if (ipcLockFD) { + close(ipcLockFD); + ipcLockFD = 0; + } +} + +#endif // IPC_USE_FILE_LOCK + +//----------------------------------------------------------------------------- +// poll list +//----------------------------------------------------------------------------- + +// +// declared in ipcdPrivate.h +// +ipcClient *ipcClients = NULL; +int ipcClientCount = 0; + +// +// the first element of this array is always zero; this is done so that the +// k'th element of ipcClientArray corresponds to the k'th element of +// ipcPollList. +// +static ipcClient ipcClientArray[IPC_MAX_CLIENTS + 1]; + +// +// element 0 contains the "server socket" +// +static PRPollDesc ipcPollList[IPC_MAX_CLIENTS + 1]; + +//----------------------------------------------------------------------------- + +static int AddClient(PRFileDesc *fd) +{ + if (ipcClientCount == IPC_MAX_CLIENTS) { + LOG(("reached maximum client limit\n")); + return -1; + } + + int pollCount = ipcClientCount + 1; + + ipcClientArray[pollCount].Init(); + + ipcPollList[pollCount].fd = fd; + ipcPollList[pollCount].in_flags = PR_POLL_READ; + ipcPollList[pollCount].out_flags = 0; + + ++ipcClientCount; + return 0; +} + +static int RemoveClient(int clientIndex) +{ + PRPollDesc *pd = &ipcPollList[clientIndex]; + + PR_Close(pd->fd); + + ipcClientArray[clientIndex].Finalize(); + + // + // keep the clients and poll_fds contiguous; move the last one into + // the spot held by the one that is going away. + // + int toIndex = clientIndex; + int fromIndex = ipcClientCount; + if (fromIndex != toIndex) { + memcpy(&ipcClientArray[toIndex], &ipcClientArray[fromIndex], sizeof(ipcClient)); + memcpy(&ipcPollList[toIndex], &ipcPollList[fromIndex], sizeof(PRPollDesc)); + } + + // + // zero out the old entries. + // + memset(&ipcClientArray[fromIndex], 0, sizeof(ipcClient)); + memset(&ipcPollList[fromIndex], 0, sizeof(PRPollDesc)); + + --ipcClientCount; + return 0; +} + +//----------------------------------------------------------------------------- + +static void PollLoop(PRFileDesc *listenFD) +{ + // the first element of ipcClientArray is unused. + memset(ipcClientArray, 0, sizeof(ipcClientArray)); + ipcClients = ipcClientArray + 1; + ipcClientCount = 0; + + ipcPollList[0].fd = listenFD; + ipcPollList[0].in_flags = PR_POLL_EXCEPT | PR_POLL_READ; + + while (1) { + PRInt32 rv; + PRIntn i; + + int pollCount = ipcClientCount + 1; + + ipcPollList[0].out_flags = 0; + + // + // poll + // + // timeout after 5 minutes. if no connections after timeout, then + // exit. this timeout ensures that we don't stay resident when no + // clients are interested in connecting after spawning the daemon. + // + // XXX add #define for timeout value + // + LOG(("calling PR_Poll [pollCount=%d]\n", pollCount)); + rv = PR_Poll(ipcPollList, pollCount, PR_SecondsToInterval(60 * 5)); + if (rv == -1) { + LOG(("PR_Poll failed [%d]\n", PR_GetError())); + return; + } + + if (rv > 0) { + // + // process clients that are ready + // + for (i = 1; i < pollCount; ++i) { + if (ipcPollList[i].out_flags != 0) { + ipcPollList[i].in_flags = + ipcClientArray[i].Process(ipcPollList[i].fd, + ipcPollList[i].out_flags); + ipcPollList[i].out_flags = 0; + } + } + + // + // cleanup any dead clients (indicated by a zero in_flags) + // + for (i = pollCount - 1; i >= 1; --i) { + if (ipcPollList[i].in_flags == 0) + RemoveClient(i); + } + + // + // check for new connection + // + if (ipcPollList[0].out_flags & PR_POLL_READ) { + LOG(("got new connection\n")); + + PRNetAddr clientAddr; + memset(&clientAddr, 0, sizeof(clientAddr)); + PRFileDesc *clientFD; + + // @todo : We need to handle errors from accept() especially something like + // EMFILE, which happens when we run out of file descriptors. + // and puts XPCOMIPCD in a poll/accept endless loop! + clientFD = PR_Accept(listenFD, &clientAddr, PR_INTERVAL_NO_WAIT); + if (clientFD == NULL) { + // ignore this error... perhaps the client disconnected. + LOG(("PR_Accept failed [%d]\n", PR_GetError())); + } + else { + // make socket non-blocking + PRSocketOptionData opt; + opt.option = PR_SockOpt_Nonblocking; + opt.value.non_blocking = PR_TRUE; + PR_SetSocketOption(clientFD, &opt); + + if (AddClient(clientFD) != 0) + PR_Close(clientFD); + } + } + } + + // + // shutdown if no clients + // + if (ipcClientCount == 0) { + LOG(("shutting down\n")); + break; + } + } +} + +//----------------------------------------------------------------------------- + +PRStatus +IPC_PlatformSendMsg(ipcClient *client, ipcMessage *msg) +{ + LOG(("IPC_PlatformSendMsg\n")); + + // + // must copy message onto send queue. + // + client->EnqueueOutboundMsg(msg); + + // + // since our Process method may have already been called, we must ensure + // that the PR_POLL_WRITE flag is set. + // + int clientIndex = client - ipcClientArray; + ipcPollList[clientIndex].in_flags |= PR_POLL_WRITE; + + return PR_SUCCESS; +} + +//----------------------------------------------------------------------------- + +int main(int argc, char **argv) +{ + PRFileDesc *listenFD = NULL; + PRNetAddr addr; + +#ifdef VBOX + /* Set up the runtime without loading the support driver. */ + RTR3InitExe(argc, &argv, 0); +#endif + + // + // ignore SIGINT so <ctrl-c> from terminal only kills the client + // which spawned this daemon. + // + signal(SIGINT, SIG_IGN); + // XXX block others? check cartman + + // ensure strict file permissions + umask(0077); + + IPC_InitLog("###"); + + LOG(("daemon started...\n")); + + //XXX uncomment these lines to test slow starting daemon + //IPC_Sleep(2); + + // set socket address + addr.local.family = PR_AF_LOCAL; + if (argc < 2) + IPC_GetDefaultSocketPath(addr.local.path, sizeof(addr.local.path)); + else + PL_strncpyz(addr.local.path, argv[1], sizeof(addr.local.path)); + +#ifdef IPC_USE_FILE_LOCK + Status status = InitDaemonDir(addr.local.path); + if (status != EOk) { + if (status == ELockFileLock) { + LOG(("Another daemon is already running, exiting.\n")); + // send a signal to the blocked parent to indicate success + IPC_NotifyParent(); + return 0; + } + else { + LOG(("InitDaemonDir failed (status=%d)\n", status)); + // don't notify the parent to cause it to fail in PR_Read() after + // we terminate +#ifdef VBOX + if (status != ELockFileOwner) + printf("Cannot create a lock file for '%s'.\n" + "Check permissions.\n", addr.local.path); +#endif + return 0; + } + } +#endif + + listenFD = PR_OpenTCPSocket(PR_AF_LOCAL); + if (!listenFD) { + LOG(("PR_OpenTCPSocket failed [%d]\n", PR_GetError())); + } + else if (PR_Bind(listenFD, &addr) != PR_SUCCESS) { + LOG(("PR_Bind failed [%d]\n", PR_GetError())); + } + else { + IPC_InitModuleReg(argv[0]); + +#ifdef VBOX + // Use large backlog, as otherwise local sockets can reject connection + // attempts. Usually harmless, but causes an unnecessary start attempt + // of IPCD (which will terminate straight away), and the next attempt + // usually succeeds. But better avoid unnecessary activities. + if (PR_Listen(listenFD, 128) != PR_SUCCESS) { +#else /* !VBOX */ + if (PR_Listen(listenFD, 5) != PR_SUCCESS) { +#endif /* !VBOX */ + LOG(("PR_Listen failed [%d]\n", PR_GetError())); + } + else { +#ifndef VBOX + // redirect all standard file descriptors to /dev/null for + // proper daemonizing + PR_Close(PR_STDIN); + PR_Open("/dev/null", O_RDONLY, 0); + PR_Close(PR_STDOUT); + PR_Open("/dev/null", O_WRONLY, 0); + PR_Close(PR_STDERR); + PR_Open("/dev/null", O_WRONLY, 0); +#endif + + IPC_NotifyParent(); + +#if defined(VBOX) && !defined(XP_OS2) + // Increase the file table size to 10240 or as high as possible. + struct rlimit lim; + if (getrlimit(RLIMIT_NOFILE, &lim) == 0) + { + if ( lim.rlim_cur < 10240 + && lim.rlim_cur < lim.rlim_max) + { + lim.rlim_cur = lim.rlim_max <= 10240 ? lim.rlim_max : 10240; + if (setrlimit(RLIMIT_NOFILE, &lim) == -1) + printf("WARNING: failed to increase file descriptor limit. (%d)\n", errno); + } + } + else + printf("WARNING: failed to obtain per-process file-descriptor limit (%d).\n", errno); +#endif + + PollLoop(listenFD); + } + + IPC_ShutdownModuleReg(); + } + + //IPC_Sleep(5); + +#ifdef IPC_USE_FILE_LOCK + // it is critical that we release the lock before closing the socket, + // otherwise, a client might launch another daemon that would be unable + // to acquire the lock and would then leave the client without a daemon. + + ShutdownDaemonDir(); +#endif + + if (listenFD) { + LOG(("closing socket\n")); + PR_Close(listenFD); + } + + return 0; +} |