diff --git a/modules/ipc/build/ipcModule.cpp b/modules/ipc/build/ipcModule.cpp index 59e7c3a676e..873ef84304e 100644 --- a/modules/ipc/build/ipcModule.cpp +++ b/modules/ipc/build/ipcModule.cpp @@ -47,9 +47,9 @@ //----------------------------------------------------------------------------- NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(ipcService, Init) -#ifndef IPC_USE_INET -#include "ipcSocketProvider.h" -NS_GENERIC_FACTORY_CONSTRUCTOR(ipcSocketProvider) +#ifdef XP_UNIX +#include "ipcSocketProviderUnix.h" +NS_GENERIC_FACTORY_CONSTRUCTOR(ipcSocketProviderUnix) #endif //----------------------------------------------------------------------------- @@ -62,11 +62,11 @@ static const nsModuleComponentInfo components[] = { IPC_SERVICE_CID, IPC_SERVICE_CONTRACTID, ipcServiceConstructor, }, -#ifndef IPC_USE_INET +#ifdef XP_UNIX { IPC_SOCKETPROVIDER_CLASSNAME, IPC_SOCKETPROVIDER_CID, - NS_NETWORK_SOCKET_CONTRACTID_PREFIX "ipc", - ipcSocketProviderConstructor, }, + NS_NETWORK_SOCKET_CONTRACTID_PREFIX IPC_SOCKET_TYPE, + ipcSocketProviderUnixConstructor, }, #endif }; diff --git a/modules/ipc/common/ipcConfig.h b/modules/ipc/common/ipcConfig.h index 40bb1ea533d..3ab309ae056 100644 --- a/modules/ipc/common/ipcConfig.h +++ b/modules/ipc/common/ipcConfig.h @@ -67,7 +67,7 @@ // #define IPC_PORT 0 #define IPC_SOCKET_TYPE "ipc" -#define IPC_DEFAULT_SOCKET_PATH "/tmp/sock" +#define IPC_DEFAULT_SOCKET_PATH "/tmp/.mozilla-ipc/ipcd" #define IPC_DAEMON_APP_NAME "mozipcd" #define IPC_PATH_SEP_CHAR '/' #endif diff --git a/modules/ipc/daemon/Makefile.in b/modules/ipc/daemon/Makefile.in index f3c0b69b43c..84a1c06742f 100644 --- a/modules/ipc/daemon/Makefile.in +++ b/modules/ipc/daemon/Makefile.in @@ -51,8 +51,12 @@ CPPSRCS = \ ipcd.cpp \ ipcClient.cpp \ ipcModuleReg.cpp \ - ipcCommandModule.cpp \ - $(NULL) + ipcCommandModule.cpp + +ifeq ($(OS_ARCH),WINNT) +else +CPPSRCS += ipcdUnix.cpp +endif PROGRAM = mozipcd$(BIN_SUFFIX) diff --git a/modules/ipc/daemon/ipcClient.cpp b/modules/ipc/daemon/ipcClient.cpp index 26dd3dece34..3c9087e1e59 100644 --- a/modules/ipc/daemon/ipcClient.cpp +++ b/modules/ipc/daemon/ipcClient.cpp @@ -35,43 +35,39 @@ * * ***** END LICENSE BLOCK ***** */ -#include -#include "prio.h" -#include "plstr.h" #include "ipcLog.h" #include "ipcClient.h" #include "ipcMessage.h" #include "ipcd.h" #include "ipcm.h" -int ipcClient::gLastID = 0; +#ifdef XP_UNIX +#include "prio.h" +#include "ipcdUnix.h" +#endif + +PRUint32 ipcClient::gLastID = 0; // // called to initialize this client context // -// return: -// PR_POLL_READ - to wait for the client socket to become readable -// PR_POLL_WRITE - to wait for the client socket to become writable +// assumptions: +// - object's memory has already been zero'd out. // -int +void ipcClient::Init() { mID = ++gLastID; mInMsg = new ipcMessage(); - mSendOffset = 0; - // every client must be able to handle IPCM messages. mTargets.Append(IPCM_TARGET); - - // wait for the client to send us a command - return PR_POLL_READ; } // // called when this client context is going away // -int +void ipcClient::Finalize() { if (mInMsg) @@ -79,9 +75,76 @@ ipcClient::Finalize() mOutMsgQ.DeleteAll(); mNames.DeleteAll(); mTargets.DeleteAll(); - return 0; } +void +ipcClient::AddName(const char *name) +{ + LOG(("adding client name: %s\n", name)); + + if (HasName(name)) + return; + + mNames.Append(name); +} + +void +ipcClient::DelName(const char *name) +{ + LOG(("deleting client name: %s\n", name)); + + mNames.FindAndDelete(name); +} + +void +ipcClient::AddTarget(const nsID &target) +{ + LOG(("adding client target\n")); + + if (HasTarget(target)) + return; + + mTargets.Append(target); +} + +void +ipcClient::DelTarget(const nsID &target) +{ + LOG(("deleting client target\n")); + + // + // cannot remove the IPCM target + // + if (!target.Equals(IPCM_TARGET)) + mTargets.FindAndDelete(target); +} + +PRBool +ipcClient::EnqueueOutboundMsg(ipcMessage *msg) +{ + LOG(("enqueue outbound message\n")); + + if (!HasTarget(msg->Target())) { + LOG((" no registered message handler\n")); + delete msg; + return PR_FALSE; + } + + mOutMsgQ.Append(msg); + +#ifdef XP_UNIX + // + // the message was successfully put on the queue... + // + // since our Process method may have already been called, we must ensure + // that the PR_POLL_WRITE flag is set. + // + IPC_ClientWritePending(this); +#endif + return PR_TRUE; +} + +#ifdef XP_UNIX // // called to process a client socket // @@ -154,63 +217,6 @@ ipcClient::Process(PRFileDesc *fd, int poll_flags) return ret_flags; } -void -ipcClient::AddName(const char *name) -{ - LOG(("adding client name: %s\n", name)); - - if (HasName(name)) - return; - - mNames.Append(name); -} - -void -ipcClient::DelName(const char *name) -{ - LOG(("deleting client name: %s\n", name)); - - mNames.FindAndDelete(name); -} - -void -ipcClient::AddTarget(const nsID &target) -{ - LOG(("adding client target\n")); - - if (HasTarget(target)) - return; - - mTargets.Append(target); -} - -void -ipcClient::DelTarget(const nsID &target) -{ - LOG(("deleting client target\n")); - - // - // cannot remove the IPCM target - // - if (!target.Equals(IPCM_TARGET)) - mTargets.FindAndDelete(target); -} - -PRBool -ipcClient::EnqueueOutboundMsg(ipcMessage *msg) -{ - LOG(("enqueue outbound message\n")); - - if (!HasTarget(msg->Target())) { - LOG((" no registered message handler\n")); - delete msg; - return PR_FALSE; - } - - mOutMsgQ.Append(msg); - return PR_TRUE; -} - // // called to write out any messages from the outgoing queue. // @@ -240,3 +246,4 @@ ipcClient::WriteMsgs(PRFileDesc *fd) return 0; } +#endif diff --git a/modules/ipc/daemon/ipcClient.h b/modules/ipc/daemon/ipcClient.h index b51e42fa7f2..a35fc5b8a5b 100644 --- a/modules/ipc/daemon/ipcClient.h +++ b/modules/ipc/daemon/ipcClient.h @@ -35,8 +35,8 @@ * * ***** END LICENSE BLOCK ***** */ -#ifndef ipcClient_h__ -#define ipcClient_h__ +#ifndef ipcClientUnix_h__ +#define ipcClientUnix_h__ #include "prio.h" #include "ipcMessageQ.h" @@ -54,11 +54,10 @@ class ipcClient { public: - int Init(); - int Finalize(); - int Process(PRFileDesc *fd, int poll_flags); + void Init(); + void Finalize(); - int ID() const { return mID; } + PRUint32 ID() const { return mID; } void AddName(const char *name); void DelName(const char *name); @@ -81,19 +80,38 @@ public: // PRBool EnqueueOutboundMsg(ipcMessage *msg); +#ifdef XP_UNIX + // + // called to process a client file descriptor. the value of pollFlags + // indicates the state of the socket. + // + // returns: + // 0 - to cancel client connection + // PR_POLL_READ - to poll for a readable socket + // PR_POLL_WRITE - to poll for a writable socket + // (both flags) - to poll for either a readable or writable socket + // + // the socket is non-blocking. + // + int Process(PRFileDesc *sockFD, int pollFlags); +#endif + private: - int WriteMsgs(PRFileDesc *fd); + static PRUint32 gLastID; - static int gLastID; - - int mID; - ipcStringList mNames; - ipcIDList mTargets; - ipcMessage *mInMsg; // buffer for incoming message - ipcMessageQ mOutMsgQ; // outgoing message queue + PRUint32 mID; + ipcStringList mNames; + ipcIDList mTargets; + ipcMessage *mInMsg; // buffer for incoming message + ipcMessageQ mOutMsgQ; // outgoing message queue +#ifdef XP_UNIX // keep track of the amount of the first message sent - PRUint32 mSendOffset; + PRUint32 mSendOffset; + + // utility function for writing out messages. + int WriteMsgs(PRFileDesc *fd); +#endif }; -#endif // !ipcClient_h__ +#endif // !ipcClientUnix_h__ diff --git a/modules/ipc/daemon/ipcModuleReg.cpp b/modules/ipc/daemon/ipcModuleReg.cpp index 49e2323d27a..b91c7a78027 100644 --- a/modules/ipc/daemon/ipcModuleReg.cpp +++ b/modules/ipc/daemon/ipcModuleReg.cpp @@ -129,12 +129,35 @@ IPC_GetModuleByID(const nsID &id) } void -IPC_InitModuleReg(const char *modulesDir) +IPC_InitModuleReg(const char *exePath) { + // + // register built-in modules + // ipcModule *module = IPC_GetCommandModule(); AddModule(module->ID(), module, NULL); - if (modulesDir) { + // + // register plug-in modules + // + if (exePath && *exePath) { + static const char relModDir[] = "ipc/modules"; + + char *p = PL_strrchr(exePath, IPC_PATH_SEP_CHAR); + if (p == NULL) { + LOG(("unexpected exe path\n")); + return; + } + + int baseLen = p - exePath; + int finalLen = baseLen + 1 + sizeof(relModDir); + + // build full path to ipc modules + char *modulesDir = (char*) malloc(finalLen); + memcpy(modulesDir, exePath, baseLen); + modulesDir[baseLen] = IPC_PATH_SEP_CHAR; + memcpy(modulesDir + baseLen + 1, relModDir, sizeof(relModDir)); + LOG(("loading libraries in %s\n", modulesDir)); // // scan directory for IPC modules diff --git a/modules/ipc/daemon/ipcModuleReg.h b/modules/ipc/daemon/ipcModuleReg.h index 64940afa346..766d3e085f8 100644 --- a/modules/ipc/daemon/ipcModuleReg.h +++ b/modules/ipc/daemon/ipcModuleReg.h @@ -41,9 +41,17 @@ #include "ipcModule.h" // -// called to init/shutdown module registry. +// called to init the module registry. +// +// params: +// exePath - path to the daemon executable. modules are loaded from a +// directory relative to the daemon executable. +// +void IPC_InitModuleReg(const char *exePath); + +// +// called to shutdown the module registry. // -void IPC_InitModuleReg(const char *moduleDir); void IPC_ShutdownModuleReg(); #endif // !ipcModuleReg_h__ diff --git a/modules/ipc/daemon/ipcd.cpp b/modules/ipc/daemon/ipcd.cpp index d7b059b4f2a..53e55bc3f93 100644 --- a/modules/ipc/daemon/ipcd.cpp +++ b/modules/ipc/daemon/ipcd.cpp @@ -35,461 +35,69 @@ * * ***** END LICENSE BLOCK ***** */ -#include -#include -#include - -#ifdef XP_UNIX -#include -#include -#include -#include -#include -#include "prprf.h" -#endif - -#include "prio.h" -#include "prerror.h" -#include "prthread.h" -#include "prinrval.h" -#include "plstr.h" - #include "ipcConfig.h" #include "ipcLog.h" #include "ipcMessage.h" #include "ipcClient.h" #include "ipcModuleReg.h" #include "ipcModule.h" +#include "ipcdPrivate.h" #include "ipcd.h" -#ifdef IPC_USE_INET -#include "prnetdb.h" -#endif - -//----------------------------------------------------------------------------- -// ipc directory and locking... -//----------------------------------------------------------------------------- - -#ifdef XP_UNIX - -static char *ipcDir; -static char *ipcSockFile; -static char *ipcLockFile; -static int ipcLockFD; - -static PRBool AcquireDaemonLock() -{ - const char lockName[] = "lock"; - - int dirLen = strlen(ipcDir); - int len = dirLen // ipcDir - + 1 // "/" - + sizeof(lockName); // "lock" - - ipcLockFile = (char *) malloc(len); - memcpy(ipcLockFile, ipcDir, dirLen); - ipcLockFile[dirLen] = '/'; - memcpy(ipcLockFile + dirLen + 1, lockName, sizeof(lockName)); - - ipcLockFD = open(ipcLockFile, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR); - if (ipcLockFD == -1) - return PR_FALSE; - - // - // 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 PR_FALSE; - - // - // write our PID into the lock file (this just seems like a good idea... - // no real purpose otherwise). - // - char buf[256]; - int nb = PR_snprintf(buf, sizeof(buf), "%u\n", (unsigned long) getpid()); - write(ipcLockFD, buf, nb); - - return PR_TRUE; -} - -static PRBool InitDaemonDir(const char *socketPath) -{ - LOG(("InitDaemonDir [sock=%s]\n", socketPath)); - - ipcSockFile = PL_strdup(socketPath); - ipcDir = PL_strdup(socketPath); - - // - // make sure IPC directory exists (XXX this should be recursive) - // - char *p = strrchr(ipcDir, '/'); - if (p) - p[0] = '\0'; - mkdir(ipcDir, 0700); - - // - // if we can't acquire the daemon lock, then another daemon - // must be active, so bail. - // - if (!AcquireDaemonLock()) - return PR_FALSE; - - // - // delete an existing socket to prevent bind from failing. - // - unlink(socketPath); - - return PR_TRUE; -} - -static void ShutdownDaemonDir() -{ - LOG(("ShutdownDaemonDir [sock=%s]\n", ipcSockFile)); - - // - // unlink files from directory before giving up lock; otherwise, we'd be - // unable to delete it w/o introducing a race condition. - // - unlink(ipcLockFile); - unlink(ipcSockFile); - - // - // should be able to remove the ipc directory now. - // - rmdir(ipcDir); - - // - // this removes the advisory lock, allowing other processes to acquire it. - // - close(ipcLockFD); - - // - // free allocated memory - // - PL_strfree(ipcDir); - PL_strfree(ipcSockFile); - free(ipcLockFile); - - ipcDir = NULL; - ipcSockFile = NULL; - ipcLockFile = NULL; -} - -#endif - -//----------------------------------------------------------------------------- -// poll list -//----------------------------------------------------------------------------- - -// upper limit on the number of active connections -// XXX may want to make this more dynamic -#define MAX_CLIENTS 100 - -// -// the first element of this array is always zero; this is done so that the -// n'th element of |clients| corresponds to the n'th element of |poll_fds|. -// -static ipcClient clients[MAX_CLIENTS + 1]; - -static PRPollDesc poll_fds[MAX_CLIENTS + 1]; -static int poll_fd_count; - -//----------------------------------------------------------------------------- - -static int AddClient(PRFileDesc *fd) -{ - if (poll_fd_count == MAX_CLIENTS + 1) { - LOG(("reached maximum client limit\n")); - return -1; - } - - poll_fds[poll_fd_count].fd = fd; - poll_fds[poll_fd_count].in_flags = clients[poll_fd_count].Init(); - poll_fds[poll_fd_count].out_flags = 0; - - ++poll_fd_count; - return 0; -} - -static int RemoveClient(int client_index) -{ - PRPollDesc *pd = &poll_fds[client_index]; - - PR_Close(pd->fd); - - clients[client_index].Finalize(); - - // - // keep the clients and poll_fds contiguous; move the last one into - // the spot held by the one that is going away. - // - int to_idx = client_index; - int from_idx = poll_fd_count - 1; - if (from_idx != to_idx) { - memcpy(&clients[to_idx], &clients[from_idx], sizeof(ipcClient)); - memcpy(&poll_fds[to_idx], &poll_fds[from_idx], sizeof(PRPollDesc)); - } - - // - // zero out the old entries. - // - memset(&clients[from_idx], 0, sizeof(ipcClient)); - memset(&poll_fds[from_idx], 0, sizeof(PRPollDesc)); - - --poll_fd_count; - return 0; -} - -//----------------------------------------------------------------------------- - -static void Process(PRFileDesc *listen_fd) -{ - poll_fd_count = 1; - - poll_fds[0].fd = listen_fd; - poll_fds[0].in_flags = PR_POLL_EXCEPT | PR_POLL_READ; - - while (1) { - PRInt32 rv; - PRIntn i; - - poll_fds[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. - // - rv = PR_Poll(poll_fds, poll_fd_count, 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 < poll_fd_count; ++i) { - if (poll_fds[i].out_flags != 0) { - poll_fds[i].in_flags = clients[i].Process(poll_fds[i].fd, poll_fds[i].out_flags); - poll_fds[i].out_flags = 0; - } - } - - // - // cleanup any dead clients (indicated by a zero in_flags) - // - for (i = poll_fd_count - 1; i >= 1; --i) { - if (poll_fds[i].in_flags == 0) - RemoveClient(i); - } - - // - // check for new connection - // - if (poll_fds[0].out_flags & PR_POLL_READ) { - LOG(("got new connection\n")); - - PRNetAddr client_addr; - memset(&client_addr, 0, sizeof(client_addr)); - PRFileDesc *client_fd; - - client_fd = PR_Accept(listen_fd, &client_addr, PR_INTERVAL_NO_WAIT); - if (client_fd == NULL) { - LOG(("PR_Accept failed [%d]\n", PR_GetError())); - return; - } - - // make socket non-blocking - PRSocketOptionData opt; - opt.option = PR_SockOpt_Nonblocking; - opt.value.non_blocking = PR_TRUE; - PR_SetSocketOption(client_fd, &opt); - - if (AddClient(client_fd) != 0) - PR_Close(client_fd); - } - } - - // - // shutdown if no clients - // - if (poll_fd_count == 1) { - LOG(("shutting down\n")); - break; - } - } -} - -//----------------------------------------------------------------------------- - -static void InitModuleReg(const char *exePath) -{ - static const char modDir[] = "ipc/modules"; - - char *p = PL_strrchr(exePath, IPC_PATH_SEP_CHAR); - if (p == NULL) { - LOG(("unexpected exe path\n")); - return; - } - - int baseLen = p - exePath; - int finalLen = baseLen + 1 + sizeof(modDir); - - // build full path to ipc modules - char *buf = (char*) malloc(finalLen); - memcpy(buf, exePath, baseLen); - buf[baseLen] = IPC_PATH_SEP_CHAR; - memcpy(buf + baseLen + 1, modDir, sizeof(modDir)); - - IPC_InitModuleReg(buf); - free(buf); -} - -int main(int argc, char **argv) -{ - PRFileDesc *listen_fd; - PRNetAddr addr; - -#ifdef XP_UNIX - signal(SIGINT, SIG_IGN); - umask(0077); // ensure strict file permissions -#endif - -#ifdef DEBUG - IPC_InitLog("###"); -#endif - -//start: -#ifdef IPC_USE_INET - listen_fd = PR_OpenTCPSocket(PR_AF_INET); - if (!listen_fd) { - LOG(("PR_OpenUDPSocket failed [%d]\n", PR_GetError())); - return -1; - } - - PR_InitializeNetAddr(PR_IpAddrLoopback, IPC_PORT, &addr); - -#else - listen_fd = PR_OpenTCPSocket(PR_AF_LOCAL); - if (!listen_fd) { - LOG(("PR_OpenUDPSocket failed [%d]\n", PR_GetError())); - return -1; - } - - const char *socket_path; - if (argc < 2) - socket_path = IPC_DEFAULT_SOCKET_PATH; - else - socket_path = argv[1]; - - if (!InitDaemonDir(socket_path)) - return 0; - - addr.local.family = PR_AF_LOCAL; - PL_strncpyz(addr.local.path, socket_path, sizeof(addr.local.path)); -#endif - - if (PR_Bind(listen_fd, &addr) != PR_SUCCESS) { - LOG(("PR_Bind failed [%d]\n", PR_GetError())); - return -1; - } - - InitModuleReg(argv[0]); - - if (PR_Listen(listen_fd, 5) != PR_SUCCESS) { - LOG(("PR_Listen failed [%d]\n", PR_GetError())); - return -1; - } - - Process(listen_fd); - - IPC_ShutdownModuleReg(); - -#ifndef IPC_USE_INET - ShutdownDaemonDir(); -#endif - - LOG(("closing socket\n")); - - if (PR_Close(listen_fd) != PR_SUCCESS) { - LOG(("PR_Close failed [%d]\n", PR_GetError())); - return -1; - } - - return 0; -} - //----------------------------------------------------------------------------- // IPC API //----------------------------------------------------------------------------- -int IPC_DispatchMsg(ipcClient *client, const ipcMessage *msg) +PRStatus +IPC_DispatchMsg(ipcClient *client, const ipcMessage *msg) { // lookup handler for this message's topic and forward message to it. ipcModule *module = IPC_GetModuleByID(msg->Target()); - if (module) + if (module) { module->HandleMsg(client, msg); - else - LOG(("no registered module; ignoring message\n")); - return 0; + return PR_SUCCESS; + } + LOG(("no registered module; ignoring message\n")); + return PR_FAILURE; } -int IPC_SendMsg(ipcClient *client, ipcMessage *msg) +PRStatus +IPC_SendMsg(ipcClient *client, ipcMessage *msg) { if (client == NULL) { int i; // // walk clients array // - for (i = 1; i < poll_fd_count - 1; ++i) - IPC_SendMsg(&clients[i], msg->Clone()); + for (i = 0; i < ipcClientCount; ++i) + IPC_SendMsg(&ipcClients[i], msg->Clone()); // send to last client w/o cloning to avoid extra malloc - IPC_SendMsg(&clients[i], msg); + IPC_SendMsg(&ipcClients[i], msg); } - else { - if (client->EnqueueOutboundMsg(msg)) { - // - // the message was successfully enqueued... - // - // since this client's Process method may have already been - // called, we need to explicitly set the PR_POLL_WRITE flag. - // - int client_index = client - clients; - poll_fds[client_index].in_flags |= PR_POLL_WRITE; - } - } - return 0; + else + client->EnqueueOutboundMsg(msg); + return PR_SUCCESS; } -ipcClient *IPC_GetClientByID(int clientID) +ipcClient * +IPC_GetClientByID(PRUint32 clientID) { // linear search OK since number of clients should be small - for (int i = 1; i < poll_fd_count; ++i) { - if (clients[i].ID() == clientID) - return &clients[i]; + for (int i = 0; i < ipcClientCount; ++i) { + if (ipcClients[i].ID() == clientID) + return &ipcClients[i]; } return NULL; } -ipcClient *IPC_GetClientByName(const char *name) +ipcClient * +IPC_GetClientByName(const char *name) { // linear search OK since number of clients should be small - for (int i = 1; i < poll_fd_count; ++i) { - if (clients[i].HasName(name)) - return &clients[i]; + for (int i = 0; i < ipcClientCount; ++i) { + if (ipcClients[i].HasName(name)) + return &ipcClients[i]; } return NULL; } @@ -528,8 +136,9 @@ IPC_EnumerateClientTargets(ipcClient *client, ipcClientTargetEnumFunc func, void } } -ipcClient *IPC_GetClients(int *count) +ipcClient * +IPC_GetClients(PRUint32 *count) { - *count = poll_fd_count - 1; - return &clients[1]; + *count = (PRUint32) ipcClientCount; + return ipcClients; } diff --git a/modules/ipc/daemon/ipcd.h b/modules/ipc/daemon/ipcd.h index 118f52adc8d..c79a6f10f4c 100644 --- a/modules/ipc/daemon/ipcd.h +++ b/modules/ipc/daemon/ipcd.h @@ -64,7 +64,7 @@ class ipcMessage; // msg - the message received. this function does not modify |msg|, // and ownership stays with the caller. // -IPC_API int IPC_DispatchMsg(ipcClient *client, const ipcMessage *msg); +IPC_API PRStatus IPC_DispatchMsg(ipcClient *client, const ipcMessage *msg); // // IPC_SendMsg @@ -75,15 +75,13 @@ IPC_API int IPC_DispatchMsg(ipcClient *client, const ipcMessage *msg); // msg - the message to be sent. this function subsumes // ownership of the message. the caller must not attempt // to access |msg| after this function returns. -// return: -// 0 - on success // -IPC_API int IPC_SendMsg(ipcClient *client, ipcMessage *msg); +IPC_API PRStatus IPC_SendMsg(ipcClient *client, ipcMessage *msg); // // returns the client ID dynamically generated for the given client. // -IPC_API int IPC_GetClientID(ipcClient *client); +IPC_API PRUint32 IPC_GetClientID(ipcClient *client); // // returns the client name (NULL if the client did not specify a name). @@ -93,7 +91,7 @@ IPC_API const char *IPC_GetClientName(ipcClient *client); // // client lookup functions // -IPC_API ipcClient *IPC_GetClientByID(int id); +IPC_API ipcClient *IPC_GetClientByID(PRUint32 id); IPC_API ipcClient *IPC_GetClientByName(const char *name); // @@ -113,7 +111,7 @@ IPC_API void IPC_EnumerateClientTargets(ipcClient *client, ipcClientTargetEnumFu // // return array of all clients, length equal to |count|. // -IPC_API ipcClient *IPC_GetClients(int *count); +IPC_API ipcClient *IPC_GetClients(PRUintn *count); // // returns the ipcModule object registered under the given module ID. diff --git a/modules/ipc/daemon/ipcdPrivate.h b/modules/ipc/daemon/ipcdPrivate.h new file mode 100644 index 00000000000..84f4a92d0cf --- /dev/null +++ b/modules/ipc/daemon/ipcdPrivate.h @@ -0,0 +1,12 @@ +#ifndef ipcdPrivate_h__ +#define ipcdPrivate_h__ + +class ipcClient; + +// +// array of connected clients +// +extern ipcClient *ipcClients; +extern int ipcClientCount; + +#endif // !ipcdPrivate_h__ diff --git a/modules/ipc/daemon/ipcdUnix.cpp b/modules/ipc/daemon/ipcdUnix.cpp new file mode 100644 index 00000000000..928108d2d69 --- /dev/null +++ b/modules/ipc/daemon/ipcdUnix.cpp @@ -0,0 +1,416 @@ +/* ***** 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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" + +//----------------------------------------------------------------------------- +// ipc directory and locking... +//----------------------------------------------------------------------------- + +static char *ipcDir; +static char *ipcSockFile; +static char *ipcLockFile; +static int ipcLockFD; + +static PRBool AcquireDaemonLock() +{ + const char lockName[] = "lock"; + + int dirLen = strlen(ipcDir); + int len = dirLen // ipcDir + + 1 // "/" + + sizeof(lockName); // "lock" + + ipcLockFile = (char *) malloc(len); + memcpy(ipcLockFile, ipcDir, dirLen); + ipcLockFile[dirLen] = '/'; + memcpy(ipcLockFile + dirLen + 1, lockName, sizeof(lockName)); + + ipcLockFD = open(ipcLockFile, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR); + if (ipcLockFD == -1) + return PR_FALSE; + + // + // 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 PR_FALSE; + + // + // write our PID into the lock file (this just seems like a good idea... + // no real purpose otherwise). + // + char buf[256]; + int nb = PR_snprintf(buf, sizeof(buf), "%u\n", (unsigned long) getpid()); + write(ipcLockFD, buf, nb); + + return PR_TRUE; +} + +static PRBool InitDaemonDir(const char *socketPath) +{ + LOG(("InitDaemonDir [sock=%s]\n", socketPath)); + + ipcSockFile = PL_strdup(socketPath); + ipcDir = PL_strdup(socketPath); + + // + // make sure IPC directory exists (XXX this should be recursive) + // + char *p = strrchr(ipcDir, '/'); + if (p) + p[0] = '\0'; + mkdir(ipcDir, 0700); + + // + // if we can't acquire the daemon lock, then another daemon + // must be active, so bail. + // + if (!AcquireDaemonLock()) + return PR_FALSE; + + // + // delete an existing socket to prevent bind from failing. + // + unlink(socketPath); + + return PR_TRUE; +} + +static void ShutdownDaemonDir() +{ + LOG(("ShutdownDaemonDir [sock=%s]\n", ipcSockFile)); + + // + // unlink files from directory before giving up lock; otherwise, we'd be + // unable to delete it w/o introducing a race condition. + // + unlink(ipcLockFile); + unlink(ipcSockFile); + + // + // should be able to remove the ipc directory now. + // + rmdir(ipcDir); + + // + // this removes the advisory lock, allowing other processes to acquire it. + // + close(ipcLockFD); + + // + // free allocated memory + // + PL_strfree(ipcDir); + PL_strfree(ipcSockFile); + free(ipcLockFile); + + ipcDir = NULL; + ipcSockFile = NULL; + ipcLockFile = NULL; +} + +//----------------------------------------------------------------------------- +// poll list +//----------------------------------------------------------------------------- + +// upper limit on the number of active connections +// XXX may want to make this more dynamic +#define MAX_CLIENTS 100 + +// +// declared in ipcdPrivate.h +// +ipcClient *ipcClients; +int ipcClientCount; + +// +// the first element of this array is always zero; this is done so that the +// n'th element of ipcClientArray corresponds to the n'th element of +// ipcPollList. +// +static ipcClient ipcClientArray[MAX_CLIENTS + 1]; + +// +// element 0 contains the "server socket" +// +static PRPollDesc ipcPollList[MAX_CLIENTS + 1]; + +//----------------------------------------------------------------------------- + +static int AddClient(PRFileDesc *fd) +{ + if (ipcClientCount == 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) +{ + 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 + // + 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; + + clientFD = PR_Accept(listenFD, &clientAddr, PR_INTERVAL_NO_WAIT); + if (clientFD == NULL) { + LOG(("PR_Accept failed [%d]\n", PR_GetError())); + return; + } + + // 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; + } + } +} + +//----------------------------------------------------------------------------- + +void +IPC_ClientWritePending(ipcClient *client) +{ + int clientIndex = client - ipcClientArray; + ipcPollList[clientIndex].in_flags |= PR_POLL_WRITE; +} + +//----------------------------------------------------------------------------- + +int main(int argc, char **argv) +{ + PRFileDesc *listenFD; + PRNetAddr addr; + + // + // ignore SIGINT so from terminal only kills the client + // which spawned this daemon. + // + signal(SIGINT, SIG_IGN); + + // ensure strict file permissions + umask(0077); + +#ifdef DEBUG + IPC_InitLog("###"); +#endif + + //XXX uncomment these lines to test slow starting daemon + //LOG(("sleeping for 2 seconds...\n")); + //PR_Sleep(PR_SecondsToInterval(2)); + + listenFD = PR_OpenTCPSocket(PR_AF_LOCAL); + if (!listenFD) { + LOG(("PR_OpenUDPSocket failed [%d]\n", PR_GetError())); + return -1; + } + + const char *socket_path; + if (argc < 2) + socket_path = IPC_DEFAULT_SOCKET_PATH; + else + socket_path = argv[1]; + + if (!InitDaemonDir(socket_path)) + return 0; + + addr.local.family = PR_AF_LOCAL; + PL_strncpyz(addr.local.path, socket_path, sizeof(addr.local.path)); + + if (PR_Bind(listenFD, &addr) != PR_SUCCESS) { + LOG(("PR_Bind failed [%d]\n", PR_GetError())); + return -1; + } + + IPC_InitModuleReg(argv[0]); + + if (PR_Listen(listenFD, 5) != PR_SUCCESS) { + LOG(("PR_Listen failed [%d]\n", PR_GetError())); + return -1; + } + + PollLoop(listenFD); + + IPC_ShutdownModuleReg(); + + ShutdownDaemonDir(); + + LOG(("closing socket\n")); + PR_Close(listenFD); + return 0; +} diff --git a/modules/ipc/src/ipcSocketProvider.cpp b/modules/ipc/daemon/ipcdUnix.h similarity index 100% rename from modules/ipc/src/ipcSocketProvider.cpp rename to modules/ipc/daemon/ipcdUnix.h diff --git a/modules/ipc/src/Makefile.in b/modules/ipc/src/Makefile.in index 6180b8c85c7..206d64df7bf 100644 --- a/modules/ipc/src/Makefile.in +++ b/modules/ipc/src/Makefile.in @@ -57,10 +57,12 @@ REQUIRES = \ CPPSRCS = \ ipcService.cpp \ - ipcTransport.cpp + ipcTransport.cpp \ + $(NULL) -ifneq ($(OS_ARCH),WINNT) -CPPSRC += ipcSocketProvider.cpp +ifeq ($(MOZ_WIDGET_TOOLKIT),windows) +else +CPPSRCS += ipcSocketProviderUnix.cpp endif LOCAL_INCLUDES = \ diff --git a/modules/ipc/src/ipcSocketProviderUnix.cpp b/modules/ipc/src/ipcSocketProviderUnix.cpp new file mode 100644 index 00000000000..743c472854d --- /dev/null +++ b/modules/ipc/src/ipcSocketProviderUnix.cpp @@ -0,0 +1,168 @@ +/* ***** 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 + * + * 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 +#include +#include + +#include "private/pprio.h" +#include "plstr.h" +#include "nsReadableUtils.h" +#include "nsMemory.h" + +#include "ipcSocketProviderUnix.h" +#include "ipcLog.h" + +static PRDescIdentity ipcIOLayerIdentity; +static PRIOMethods ipcIOLayerMethods; +static char *ipcIOSocketPath; + +static PRStatus PR_CALLBACK +ipcIOLayerConnect(PRFileDesc* fd, const PRNetAddr* a, PRIntervalTime timeout) +{ + if (!fd || !fd->lower) + return PR_FAILURE; + + PRStatus status; + + if (ipcIOSocketPath == NULL || ipcIOSocketPath[0] == '\0') { + NS_ERROR("not initialized"); + return PR_FAILURE; + } + + // + // ignore passed in address. + // + PRNetAddr addr; + addr.local.family = PR_AF_LOCAL; + PL_strncpyz(addr.local.path, ipcIOSocketPath, sizeof(addr.local.path)); + + status = fd->lower->methods->connect(fd->lower, &addr, timeout); + if (status != PR_SUCCESS) + return status; + + // + // now that we have a connected socket; do some security checks on the + // file descriptor. + // + // (1) make sure owner matches + // (2) make sure permissions match expected permissions + // + // if these conditions aren't met then bail. + // + int unix_fd = PR_FileDesc2NativeHandle(fd->lower); + + struct stat st; + if (fstat(unix_fd, &st) == -1) { + NS_ERROR("stat failed"); + return PR_FAILURE; + } + + if (st.st_uid != getuid() && st.st_uid != geteuid()) { + NS_ERROR("userid check failed"); + return PR_FAILURE; + } + + return PR_SUCCESS; +} + +static void InitIPCMethods() +{ + ipcIOLayerIdentity = PR_GetUniqueIdentity("moz:ipc-layer"); + + ipcIOLayerMethods = *PR_GetDefaultIOMethods(); + ipcIOLayerMethods.connect = ipcIOLayerConnect; +} + +NS_IMETHODIMP +ipcSocketProviderUnix::NewSocket(const char *host, + PRInt32 port, + const char *proxyHost, + PRInt32 proxyPort, + PRFileDesc **fd, + nsISupports **securityInfo) +{ + static PRBool firstTime = PR_TRUE; + if (firstTime) { + InitIPCMethods(); + firstTime = PR_FALSE; + } + + PRFileDesc *sock = PR_OpenTCPSocket(PR_AF_LOCAL); + if (!sock) return NS_ERROR_OUT_OF_MEMORY; + + PRFileDesc *layer = PR_CreateIOLayerStub(ipcIOLayerIdentity, &ipcIOLayerMethods); + if (!layer) + goto loser; + layer->secret = NULL; + + if (PR_PushIOLayer(sock, PR_GetLayersIdentity(sock), layer) != PR_SUCCESS) + goto loser; + + *fd = sock; + return NS_OK; + +loser: + if (layer) + layer->dtor(layer); + if (sock) + PR_Close(sock); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +ipcSocketProviderUnix::AddToSocket(const char *host, + PRInt32 port, + const char *proxyHost, + PRInt32 proxyPort, + PRFileDesc *fd, + nsISupports **securityInfo) +{ + NS_NOTREACHED("unexpected"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMPL_THREADSAFE_ISUPPORTS1(ipcSocketProviderUnix, nsISocketProvider) + + +void +ipcSocketProviderUnix::SetSocketPath(const nsACString &socketPath) +{ + if (ipcIOSocketPath) + nsMemory::Free(ipcIOSocketPath); + ipcIOSocketPath = ToNewCString(socketPath); +} diff --git a/modules/ipc/src/ipcSocketProvider.h b/modules/ipc/src/ipcSocketProviderUnix.h similarity index 100% rename from modules/ipc/src/ipcSocketProvider.h rename to modules/ipc/src/ipcSocketProviderUnix.h diff --git a/modules/ipc/src/ipcTransport.cpp b/modules/ipc/src/ipcTransport.cpp index 60313a6ffda..aba7b046127 100644 --- a/modules/ipc/src/ipcTransport.cpp +++ b/modules/ipc/src/ipcTransport.cpp @@ -56,6 +56,10 @@ #include "ipcTransport.h" #include "ipcm.h" +#ifdef XP_UNIX +#include "ipcSocketProviderUnix.h" +#endif + static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID); //----------------------------------------------------------------------------- @@ -77,6 +81,10 @@ ipcTransport::Init(const nsACString &appName, mSocketPath = socketPath; mObserver = obs; +#ifdef XP_UNIX + ipcSocketProviderUnix::SetSocketPath(socketPath); +#endif + LOG(("ipcTransport::Init [app-name=%s]\n", mAppName.get())); // XXX service should be the observer @@ -237,6 +245,7 @@ ipcTransport::OnStopRequest(nsIRequest *req, nsresult status) LOG((" failed to spawn daemon [rv=%x]\n", rv)); return; } + mSpawnedDaemon = PR_TRUE; // // re-initialize connection after timeout @@ -247,8 +256,10 @@ ipcTransport::OnStopRequest(nsIRequest *req, nsresult status) return; } - // use a simple exponential growth algorithm n*2^(n-1) - PRUint32 ms = 1000 * (1 << (mConnectionAttemptCount - 1)); + // use a simple exponential growth algorithm 2^(n-1) + PRUint32 ms = 500 * (1 << (mConnectionAttemptCount - 1)); + if (ms > 10000) + ms = 10000; LOG((" waiting %u milliseconds\n", ms)); @@ -283,24 +294,15 @@ ipcTransport::CreateTransport() 1024, 1024*16, getter_AddRefs(mTransport)); - if (NS_FAILED(rv)) return rv; - -#ifndef IPC_USE_INET - nsCOMPtr st(do_QueryInterface(mTransport, &rv)); - if (NS_FAILED(rv)) return rv; - - PRNetAddr addr; - addr.local.family = PR_AF_LOCAL; - PL_strncpyz(addr.local.path, mSocketPath.get(), sizeof(addr.local.path)); - - rv = st->SetAddress(&addr); -#endif return rv; } nsresult ipcTransport::SpawnDaemon() { + if (mSpawnedDaemon) + return NS_OK; + LOG(("ipcTransport::SpawnDaemon\n")); nsresult rv; diff --git a/modules/ipc/src/ipcTransport.h b/modules/ipc/src/ipcTransport.h index 83dac36901e..c912e549914 100644 --- a/modules/ipc/src/ipcTransport.h +++ b/modules/ipc/src/ipcTransport.h @@ -130,6 +130,7 @@ public: , mWriteSuspended(PR_FALSE) , mSentHello(PR_FALSE) , mHaveConnection(PR_FALSE) + , mSpawnedDaemon(PR_FALSE) , mConnectionAttemptCount(0) { } virtual ~ipcTransport(); @@ -178,6 +179,7 @@ private: PRPackedBool mWriteSuspended; PRPackedBool mSentHello; PRPackedBool mHaveConnection; + PRPackedBool mSpawnedDaemon; PRUint8 mConnectionAttemptCount; };