diff options
Diffstat (limited to '')
-rw-r--r-- | src/libs/xpcom18a4/ipc/ipcd/client/src/ipcConnectionUnix.cpp | 615 |
1 files changed, 615 insertions, 0 deletions
diff --git a/src/libs/xpcom18a4/ipc/ipcd/client/src/ipcConnectionUnix.cpp b/src/libs/xpcom18a4/ipc/ipcd/client/src/ipcConnectionUnix.cpp new file mode 100644 index 00000000..5cd7a0c8 --- /dev/null +++ b/src/libs/xpcom18a4/ipc/ipcd/client/src/ipcConnectionUnix.cpp @@ -0,0 +1,615 @@ +/* vim:set ts=2 sw=2 et cindent: */ +/* ***** 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 IBM Corporation. + * Portions created by IBM Corporation are Copyright (C) 2003 + * IBM Corporation. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher <darin@meer.net> + * + * 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 "private/pprio.h" +#include "prerror.h" +#include "prthread.h" +#include "prlock.h" +#include "prlog.h" // for PR_ASSERT (we don't actually use NSPR logging) +#include "prio.h" + +#include "ipcConnection.h" +#include "ipcMessageQ.h" +#include "ipcConfig.h" +#include "ipcLog.h" + +#ifdef VBOX +# include "prenv.h" +# include <stdio.h> +# include <VBox/log.h> +#endif + + +//----------------------------------------------------------------------------- +// NOTE: this code does not need to link with anything but NSPR. that is by +// design, so it can be easily reused in other projects that want to +// talk with mozilla's IPC daemon, but don't want to depend on xpcom. +// we depend at most on some xpcom header files, but no xpcom runtime +// symbols are used. +//----------------------------------------------------------------------------- + + +// single user systems, like OS/2, don't need these security checks. +#ifndef XP_OS2 +#define IPC_SKIP_SECURITY_CHECKS +#endif + +#ifndef IPC_SKIP_SECURITY_CHECKS +#include <unistd.h> +#include <sys/stat.h> +#endif + +static PRStatus +DoSecurityCheck(PRFileDesc *fd, const char *path) +{ +#ifndef IPC_SKIP_SECURITY_CHECKS + // + // 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); + + struct stat st; + if (fstat(unix_fd, &st) == -1) { + LOG(("stat failed")); + return PR_FAILURE; + } + + if (st.st_uid != getuid() && st.st_uid != geteuid()) { + // + // on OSX 10.1.5, |fstat| has a bug when passed a file descriptor to + // a socket. it incorrectly returns a UID of 0. however, |stat| + // succeeds, but using |stat| introduces a race condition. + // + // XXX come up with a better security check. + // + if (st.st_uid != 0) { + LOG(("userid check failed")); + return PR_FAILURE; + } + if (stat(path, &st) == -1) { + LOG(("stat failed")); + return PR_FAILURE; + } + if (st.st_uid != getuid() && st.st_uid != geteuid()) { + LOG(("userid check failed")); + return PR_FAILURE; + } + } +#endif + return PR_SUCCESS; +} + +//----------------------------------------------------------------------------- + +struct ipcCallback : public ipcListNode<ipcCallback> +{ + ipcCallbackFunc func; + void *arg; +}; + +typedef ipcList<ipcCallback> ipcCallbackQ; + +//----------------------------------------------------------------------------- + +struct ipcConnectionState +{ + PRLock *lock; + PRPollDesc fds[2]; + ipcCallbackQ callback_queue; + ipcMessageQ send_queue; + PRUint32 send_offset; // amount of send_queue.First() already written. + ipcMessage *in_msg; + PRBool shutdown; +}; + +#define SOCK 0 +#define POLL 1 + +static void +ConnDestroy(ipcConnectionState *s) +{ + if (s->lock) + PR_DestroyLock(s->lock); + + if (s->fds[SOCK].fd) + PR_Close(s->fds[SOCK].fd); + + if (s->fds[POLL].fd) + PR_DestroyPollableEvent(s->fds[POLL].fd); + + if (s->in_msg) + delete s->in_msg; + + s->send_queue.DeleteAll(); + delete s; +} + +static ipcConnectionState * +ConnCreate(PRFileDesc *fd) +{ + ipcConnectionState *s = new ipcConnectionState; + if (!s) + return NULL; + + s->lock = PR_NewLock(); + s->fds[SOCK].fd = NULL; + s->fds[POLL].fd = PR_NewPollableEvent(); + s->send_offset = 0; + s->in_msg = NULL; + s->shutdown = PR_FALSE; + + if (!s->lock || !s->fds[1].fd) + { + ConnDestroy(s); + return NULL; + } + + // disable inheritance of the IPC socket by children started + // using non-NSPR process API + PRStatus status = PR_SetFDInheritable(fd, PR_FALSE); + if (status != PR_SUCCESS) + { + LOG(("coudn't make IPC socket non-inheritable [err=%d]\n", PR_GetError())); + return NULL; + } + + // store this only if we are going to succeed. + s->fds[SOCK].fd = fd; + + return s; +} + +static nsresult +ConnRead(ipcConnectionState *s) +{ + char buf[1024]; + nsresult rv = NS_OK; + PRInt32 n; + + do + { + n = PR_Read(s->fds[SOCK].fd, buf, sizeof(buf)); + if (n < 0) + { + PRErrorCode err = PR_GetError(); + if (err == PR_WOULD_BLOCK_ERROR) + { + // socket is empty... we need to go back to polling. + break; + } + LOG(("PR_Read returned failure [err=%d]\n", err)); + rv = NS_ERROR_UNEXPECTED; + } + else if (n == 0) + { + LOG(("PR_Read returned EOF\n")); + rv = NS_ERROR_UNEXPECTED; + } + else + { + const char *pdata = buf; + while (n) + { + PRUint32 bytesRead; + PRBool complete; + + if (!s->in_msg) + { + s->in_msg = new ipcMessage; + if (!s->in_msg) + { + rv = NS_ERROR_OUT_OF_MEMORY; + break; + } + } + + if (s->in_msg->ReadFrom(pdata, n, &bytesRead, &complete) != PR_SUCCESS) + { + LOG(("error reading IPC message\n")); + rv = NS_ERROR_UNEXPECTED; + break; + } + + PR_ASSERT(PRUint32(n) >= bytesRead); + n -= bytesRead; + pdata += bytesRead; + + if (complete) + { + // protect against weird re-entrancy cases... + ipcMessage *m = s->in_msg; + s->in_msg = NULL; + + IPC_OnMessageAvailable(m); + } + } + } + } + while (NS_SUCCEEDED(rv)); + + return rv; +} + +static nsresult +ConnWrite(ipcConnectionState *s) +{ + nsresult rv = NS_OK; + + PR_Lock(s->lock); + + // write one message and then return. + if (s->send_queue.First()) + { + PRInt32 n = PR_Write(s->fds[SOCK].fd, + s->send_queue.First()->MsgBuf() + s->send_offset, + s->send_queue.First()->MsgLen() - s->send_offset); + if (n <= 0) + { + PRErrorCode err = PR_GetError(); + if (err == PR_WOULD_BLOCK_ERROR) + { + // socket is full... we need to go back to polling. + } + else + { + LOG(("error writing to socket [err=%d]\n", err)); + rv = NS_ERROR_UNEXPECTED; + } + } + else + { + s->send_offset += n; + if (s->send_offset == s->send_queue.First()->MsgLen()) + { + s->send_queue.DeleteFirst(); + s->send_offset = 0; + + // if the send queue is empty, then we need to stop trying to write. + if (s->send_queue.IsEmpty()) + s->fds[SOCK].in_flags &= ~PR_POLL_WRITE; + } + } + } + + PR_Unlock(s->lock); + return rv; +} + +PR_STATIC_CALLBACK(void) +ConnThread(void *arg) +{ + PRInt32 num; + nsresult rv = NS_OK; + + ipcConnectionState *s = (ipcConnectionState *) arg; + + // we monitor two file descriptors in this thread. the first (at index 0) is + // the socket connection with the IPC daemon. the second (at index 1) is the + // pollable event we monitor in order to know when to send messages to the + // IPC daemon. + + s->fds[SOCK].in_flags = PR_POLL_READ; + s->fds[POLL].in_flags = PR_POLL_READ; + + while (NS_SUCCEEDED(rv)) + { + s->fds[SOCK].out_flags = 0; + s->fds[POLL].out_flags = 0; + + // + // poll on the IPC socket and NSPR pollable event + // + num = PR_Poll(s->fds, 2, PR_INTERVAL_NO_TIMEOUT); + if (num > 0) + { + ipcCallbackQ cbs_to_run; + + // check if something has been added to the send queue. if so, then + // acknowledge pollable event (wait should not block), and configure + // poll flags to find out when we can write. + + if (s->fds[POLL].out_flags & PR_POLL_READ) + { + PR_WaitForPollableEvent(s->fds[POLL].fd); + PR_Lock(s->lock); + + if (!s->send_queue.IsEmpty()) + s->fds[SOCK].in_flags |= PR_POLL_WRITE; + + if (!s->callback_queue.IsEmpty()) + s->callback_queue.MoveTo(cbs_to_run); + + PR_Unlock(s->lock); + } + + // check if we can read... + if (s->fds[SOCK].out_flags & PR_POLL_READ) + rv = ConnRead(s); + + // check if we can write... + if (s->fds[SOCK].out_flags & PR_POLL_WRITE) + rv = ConnWrite(s); + + // check if we have callbacks to run + while (!cbs_to_run.IsEmpty()) + { + ipcCallback *cb = cbs_to_run.First(); + (cb->func)(cb->arg); + cbs_to_run.DeleteFirst(); + } + + // check if we should exit this thread. delay processing a shutdown + // request until after all queued up messages have been sent and until + // after all queued up callbacks have been run. + PR_Lock(s->lock); + if (s->shutdown && s->send_queue.IsEmpty() && s->callback_queue.IsEmpty()) + rv = NS_ERROR_ABORT; + PR_Unlock(s->lock); + } + else + { + LOG(("PR_Poll returned error %d (%s), os error %d\n", PR_GetError(), + PR_ErrorToName(PR_GetError()), PR_GetOSError())); + rv = NS_ERROR_UNEXPECTED; + } + } + + // notify termination of the IPC connection + if (rv == NS_ERROR_ABORT) + rv = NS_OK; + IPC_OnConnectionEnd(rv); + + LOG(("IPC thread exiting\n")); +} + +//----------------------------------------------------------------------------- +// IPC connection API +//----------------------------------------------------------------------------- + +static ipcConnectionState *gConnState = NULL; +static PRThread *gConnThread = NULL; + +#ifdef DEBUG +static PRThread *gMainThread = NULL; +#endif + +nsresult +TryConnect(PRFileDesc **result) +{ + PRFileDesc *fd; + PRNetAddr addr; + PRSocketOptionData opt; + // don't use NS_ERROR_FAILURE as we want to detect these kind of errors + // in the frontend + nsresult rv = NS_ERROR_SOCKET_FAIL; + + fd = PR_OpenTCPSocket(PR_AF_LOCAL); + if (!fd) + goto end; + + addr.local.family = PR_AF_LOCAL; + IPC_GetDefaultSocketPath(addr.local.path, sizeof(addr.local.path)); + + // blocking connect... will fail if no one is listening. + if (PR_Connect(fd, &addr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) + goto end; + +#ifdef VBOX + if (PR_GetEnv("TESTBOX_UUID")) + fprintf(stderr, "IPC socket path: %s\n", addr.local.path); + LogRel(("IPC socket path: %s\n", addr.local.path)); +#endif + + // make socket non-blocking + opt.option = PR_SockOpt_Nonblocking; + opt.value.non_blocking = PR_TRUE; + PR_SetSocketOption(fd, &opt); + + // do some security checks on connection socket... + if (DoSecurityCheck(fd, addr.local.path) != PR_SUCCESS) + goto end; + + *result = fd; + return NS_OK; + +end: + if (fd) + PR_Close(fd); + + return rv; +} + +nsresult +IPC_Connect(const char *daemonPath) +{ + // synchronous connect, spawn daemon if necessary. + + PRFileDesc *fd = NULL; + nsresult rv = NS_ERROR_FAILURE; + + if (gConnState) + return NS_ERROR_ALREADY_INITIALIZED; + + // + // here's the connection algorithm: try to connect to an existing daemon. + // if the connection fails, then spawn the daemon (wait for it to be ready), + // and then retry the connection. it is critical that the socket used to + // connect to the daemon not be inherited (this causes problems on RH9 at + // least). + // + + rv = TryConnect(&fd); + if (NS_FAILED(rv)) + { + nsresult rv1 = IPC_SpawnDaemon(daemonPath); + if (NS_SUCCEEDED(rv1) || rv != NS_ERROR_SOCKET_FAIL) + rv = rv1; + if (NS_SUCCEEDED(rv)) + rv = TryConnect(&fd); + } + + if (NS_FAILED(rv)) + goto end; + + // + // ok, we have a connection to the daemon! + // + + // build connection state object + gConnState = ConnCreate(fd); + if (!gConnState) + { + rv = NS_ERROR_OUT_OF_MEMORY; + goto end; + } + fd = NULL; // connection state now owns the socket + + gConnThread = PR_CreateThread(PR_USER_THREAD, + ConnThread, + gConnState, + PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, + 0); + if (!gConnThread) + { + rv = NS_ERROR_OUT_OF_MEMORY; + goto end; + } + +#ifdef DEBUG + gMainThread = PR_GetCurrentThread(); +#endif + return NS_OK; + +end: + if (gConnState) + { + ConnDestroy(gConnState); + gConnState = NULL; + } + if (fd) + PR_Close(fd); + return rv; +} + +nsresult +IPC_Disconnect() +{ + // Must disconnect on same thread used to connect! + PR_ASSERT(gMainThread == PR_GetCurrentThread()); + + if (!gConnState || !gConnThread) + return NS_ERROR_NOT_INITIALIZED; + + PR_Lock(gConnState->lock); + gConnState->shutdown = PR_TRUE; + PR_SetPollableEvent(gConnState->fds[POLL].fd); + PR_Unlock(gConnState->lock); + + PR_JoinThread(gConnThread); + + ConnDestroy(gConnState); + + gConnState = NULL; + gConnThread = NULL; + return NS_OK; +} + +nsresult +IPC_SendMsg(ipcMessage *msg) +{ + if (!gConnState || !gConnThread) + return NS_ERROR_NOT_INITIALIZED; + + PR_Lock(gConnState->lock); + gConnState->send_queue.Append(msg); + PR_SetPollableEvent(gConnState->fds[POLL].fd); + PR_Unlock(gConnState->lock); + + return NS_OK; +} + +nsresult +IPC_DoCallback(ipcCallbackFunc func, void *arg) +{ + if (!gConnState || !gConnThread) + return NS_ERROR_NOT_INITIALIZED; + + ipcCallback *callback = new ipcCallback; + if (!callback) + return NS_ERROR_OUT_OF_MEMORY; + callback->func = func; + callback->arg = arg; + + PR_Lock(gConnState->lock); + gConnState->callback_queue.Append(callback); + PR_SetPollableEvent(gConnState->fds[POLL].fd); + PR_Unlock(gConnState->lock); + return NS_OK; +} + +//----------------------------------------------------------------------------- + +#ifdef TEST_STANDALONE + +void IPC_OnConnectionFault(nsresult rv) +{ + LOG(("IPC_OnConnectionFault [rv=%x]\n", rv)); +} + +void IPC_OnMessageAvailable(ipcMessage *msg) +{ + LOG(("IPC_OnMessageAvailable\n")); + delete msg; +} + +int main() +{ + IPC_InitLog(">>>"); + IPC_Connect("/builds/moz-trunk/seamonkey-debug-build/dist/bin/mozilla-ipcd"); + IPC_Disconnect(); + return 0; +} + +#endif |