initial work on cthreads functionality for NSPR RHAPSODY_NSPR_BRANCH
authortoshok
Tue, 05 May 1998 09:49:30 +0000
branchRHAPSODY_NSPR_BRANCH
changeset 83 04bcb0e10bbf58c32ffa0c7923c712b505d0b710
parent 56 070915eb1e7773e9397e75268074d022b87f41ad
child 84 1d186e13b6fe9b0649f06d3cd003dc57156dcca1
push idunknown
push userunknown
push dateunknown
initial work on cthreads functionality for NSPR
pr/src/cthreads/Makefile
pr/src/cthreads/ctio.c
pr/src/cthreads/ctmisc.c
pr/src/cthreads/ctsynch.c
pr/src/cthreads/ctthread.c
new file mode 100644
--- /dev/null
+++ b/pr/src/cthreads/Makefile
@@ -0,0 +1,40 @@
+#
+# The contents of this file are subject to the Netscape Public License
+# Version 1.0 (the "NPL"); you may not use this file except in
+# compliance with the NPL.  You may obtain a copy of the NPL at
+# http://www.mozilla.org/NPL/
+#
+# Software distributed under the NPL is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
+# for the specific language governing rights and limitations under the
+# NPL.
+#
+# The Initial Developer of this code under the NPL is Netscape
+# Communications Corporation.  Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation.  All Rights
+# Reserved.
+#
+
+#! gmake
+
+MOD_DEPTH = ../../..
+
+include $(MOD_DEPTH)/config/config.mk
+
+CSRCS = \
+	ctio.c \
+	ctsynch.c \
+	ctthread.c \
+	ctmisc.c \
+	$(NULL)
+
+TARGETS	= $(OBJS)
+
+INCLUDES = -I$(DIST)/include/private -I$(DIST)/include
+
+include $(MOD_DEPTH)/config/rules.mk
+
+export:: $(TARGETS)
+
+install:: export
+
new file mode 100644
--- /dev/null
+++ b/pr/src/cthreads/ctio.c
@@ -0,0 +1,2823 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * The contents of this file are subject to the Netscape Public License
+ * Version 1.0 (the "NPL"); you may not use this file except in
+ * compliance with the NPL.  You may obtain a copy of the NPL at
+ * http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the NPL is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
+ * for the specific language governing rights and limitations under the
+ * NPL.
+ *
+ * The Initial Developer of this code under the NPL is Netscape
+ * Communications Corporation.  Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
+ * Reserved.
+ */
+
+/*
+** File:   ctio.c
+** Descritpion:  Implemenation of I/O methods for cthreads
+** Exports:   ctio.h
+*/
+
+#if defined(_PR_CTHREADS)
+
+#include <mach/cthreads.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+/* To pick up getrlimit() etc. */
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include "primpl.h"
+
+#include <netinet/tcp.h>  /* TCP_NODELAY, TCP_MAXSEG */
+
+#if defined(RHAPSODY)
+#define _PRSize_t int
+#else
+#error "Cannot determine architecture"
+#endif
+
+#if defined(RHAPSODY)
+#define _PRSockOptVal_t void *
+#else
+#error "Cannot determine architecture"
+#endif
+
+#if defined(RHAPSODY)
+#define _PRSelectFdSetArg_t fd_set *
+#else
+#error "Cannot determine architecture"
+#endif
+
+static PRFileDesc *ct_SetMethods(PRIntn osfd, PRDescType type);
+
+static PRLock *_pr_flock_lock;  /* For PR_LockFile() etc. */
+static PRLock *_pr_rename_lock;  /* For PR_Rename() */
+
+extern struct _CT_Bookeeping ct_book;  /* defined in ptthread.c */
+extern PRIntn ct_schedpriv;  /* defined in ptthread.c */
+
+#if defined(_PR_INET6)
+extern PRBool _pr_ipv6_enabled;  /* defined in prnetdb.c */
+#endif
+
+/*****************************************************************************/
+/************************* I/O Continuation machinery ************************/
+/*****************************************************************************/
+
+/*
+ * The polling interval defines the maximum amount of time that a thread
+ * might hang up before an interrupt is noticed.
+ */
+#define CT_DEFAULT_POLL_MSEC 100
+
+typedef struct ct_Continuation ct_Continuation;
+typedef PRBool (*ContinuationFn)(ct_Continuation *op, PRInt16 revents);
+
+typedef enum pr_ContuationStatus
+{
+    ct_continuation_sumbitted,
+    ct_continuation_inprogress,
+    ct_continuation_abort,
+    ct_continuation_done
+} pr_ContuationStatus;
+
+struct ct_Continuation
+{
+    /* These objects are linked in ascending timeout order */
+    ct_Continuation *next, *prev;           /* self linked list of these things */
+
+    /* The building of the continuation operation */
+    ContinuationFn function;                /* what function to continue */
+    union { PRIntn osfd; } arg1;            /* #1 - the op's fd */
+    union { void* buffer; } arg2;           /* #2 - primary transfer buffer */
+    union {
+        _PRSize_t amount;                   /* #3 - size of 'buffer', or */
+        _PRSize_t *addr_len;                /*    - length of address */
+    } arg3;
+    union { PRIntn flags; } arg4;           /* #4 - read/write flags */
+    union { PRNetAddr *addr; } arg5;        /* #5 - send/recv address */
+    
+    PRIntervalTime timeout;                 /* client (relative) timeout */
+    PRIntervalTime absolute;                /* internal (absolute) timeout */
+
+    PRInt16 event;                           /* flags for poll()'s events */
+
+    /*
+    ** The representation and notification of the results of the operation.
+    ** These function can either return an int return code or a pointer to
+    ** some object.
+    */
+    union { _PRSize_t code; void *object; } result;
+
+    PRIntn syserrno;                        /* in case it failed, why (errno) */
+    pr_ContuationStatus status;             /* the status of the operation */
+    PRCondVar *complete;                    /* to notify the initiating thread */
+};
+
+static struct ct_TimedQueue
+{
+    PRLock *ml;                             /* a little protection */
+    PRThread *thread;                       /* internal thread's identification */
+    PRCondVar *new_op;                      /* new operation supplied */
+    PRUintn op_count;                       /* number of operations in the list */
+    ct_Continuation *head, *tail;           /* head/tail of list of operations */
+
+    ct_Continuation *op;                    /* timed operation furthest in future */
+    PRBool exitFlag;                        /* a Boolean flag for signaling the
+                                             * continuation thread to exit */
+} ct_tq;
+
+#if defined(DEBUG)
+static struct ct_debug_s
+{
+    PRUintn predictionsFoiled;
+    PRUintn pollingListMax;
+    PRUintn continuationsServed;
+} ct_debug;
+#endif  /* DEBUG */
+
+/*
+ * The following two functions, ct_InsertTimedInternal and
+ * ct_FinishTimedInternal, are always called with the ct_tq.ml
+ * lock held.  The "internal" in the functions' names come from
+ * the Mesa programming language.  Internal functions are always
+ * called inside a monitor.
+ */
+
+static void ct_InsertTimedInternal(ct_Continuation *op)
+{
+    ct_Continuation *t_op = NULL;
+    PRIntervalTime now = PR_IntervalNow();
+
+    /*
+     * If this element operation isn't timed, it gets queued at the
+     * end of the list (just after ct_tq.tail) and we're
+     * finishd early.
+     */
+    if (PR_INTERVAL_NO_TIMEOUT == op->timeout)
+    {
+        t_op = ct_tq.tail;  /* put it at the end */
+        goto done;
+    }
+
+    /*
+     * The portion of this routine deals with timed ops.
+     */
+    op->absolute = now + op->timeout;  /* absolute ticks */
+    if (NULL == ct_tq.op) ct_tq.op = op;
+    else
+    {
+        /*
+         * To find where in the list to put the new operation, based
+         * on the absolute time the operation in question will expire.
+         *
+         * The new operation ('op') will expire at now() + op->timeout.
+         *
+         * This should be easy!
+         */
+
+        for (t_op = ct_tq.op; NULL != t_op; t_op = t_op->prev)
+        {
+            /*
+             * If 'op' expires later than t_op, then insert 'op' just
+             * ahead of t_op. Otherwise, compute when operation[n-1]
+             * expires and try again.
+             *
+             * The actual different between the expiriation of 'op'
+             * and the current operation what becomes the new operaton's
+             * timeout interval. That interval is also subtracted from
+             * the interval of the operation immediately following where
+             * we stick 'op' (unless the next one isn't timed). The new
+             * timeout assigned to 'op' takes into account the values of
+             * now() and when the previous intervals were computed.
+             */
+            if ((PRInt32)(op->absolute - t_op->absolute) >= 0)
+            {
+                if (t_op == ct_tq.op) ct_tq.op = op;
+                break;
+            }
+        }
+    }
+
+done:
+
+    /*
+     * Insert 'op' into the queue just after t_op or if t_op is null,
+     * at the head of the list.
+     *
+     * We need to set up the 'next' and 'prev' pointers of 'op'
+     * correctly before inserting 'op' into the queue.  Also, we insert
+     * 'op' by updating ct_tq.head or op->prev->next first, and then
+     * updating op->next->prev.  We want to make sure that the 'next'
+     * pointers are linked up correctly at all times so that we can
+     * traverse the queue by starting with ct_tq.head and following
+     * the 'next' pointers, without having to acquire the ct_tq.ml lock.
+     * (We do that in ContinuationThread.)  We traverse the 'prev'
+     * pointers only in this function, which is called with the lock held.
+     *
+     * Similar care is taken in ct_FinishTimedInternal where we remove
+     * an op from the queue.
+     */
+    if (NULL == t_op)
+    {
+        op->prev = NULL;
+        op->next = ct_tq.head;
+        ct_tq.head = op;
+        if (NULL == ct_tq.tail) ct_tq.tail = op;
+        else op->next->prev = op;
+    }
+    else
+    {
+        op->prev = t_op;
+        op->next = t_op->next;
+        if (NULL != op->prev)
+            op->prev->next = op;
+        if (NULL != op->next)
+            op->next->prev = op;
+        if (t_op == ct_tq.tail)
+            ct_tq.tail = op;
+    }
+
+    ct_tq.op_count += 1;
+
+}  /* ct_InsertTimedInternal */
+
+/*
+ * function: ct_FinishTimedInternal
+ *
+ * Takes the finished operation out of the timed queue. It
+ * notifies the initiating thread that the opertions is
+ * complete and returns to the caller the value of the next
+ * operation in the list (or NULL).
+ */
+static ct_Continuation *ct_FinishTimedInternal(ct_Continuation *op)
+{
+    ct_Continuation *next;
+
+    /* remove this one from the list */
+    if (NULL == op->prev) ct_tq.head = op->next;
+    else op->prev->next = op->next;
+    if (NULL == op->next) ct_tq.tail = op->prev;
+    else op->next->prev = op->prev;
+
+    /* did we happen to hit the timed op? */
+    if (op == ct_tq.op) ct_tq.op = op->prev;
+
+    next = op->next;
+    op->next = op->prev = NULL;
+    op->status = ct_continuation_done;
+
+    ct_tq.op_count -= 1;
+
+#if defined(DEBUG)
+    ct_debug.continuationsServed += 1;
+#endif
+    PR_NotifyCondVar(op->complete);
+
+    return next;
+}  /* ct_FinishTimedInternal */
+
+static void ContinuationThread(void *arg)
+{
+    /* initialization */
+    PRInt32 msecs, mx_poll_ticks;
+    struct pollfd *pollingList = 0;         /* list built for polling */
+    PRIntn pollingListUsed;                 /* # entries used in the list */
+    PRIntn pollingListNeeded;               /* # entries needed this time */
+    PRIntn pollingSlotsAllocated = 0;       /* # entries available in list */
+    
+    mx_poll_ticks = (PRInt32)PR_MillisecondsToInterval(CT_DEFAULT_POLL_MSEC);
+
+    /* do some real work */
+    while (1)
+    {
+        PRIntn rv;
+        PRInt32 timeout;
+        PRIntn pollIndex;
+        PRIntervalTime now;
+        ct_Continuation *op;
+
+        PR_Lock(ct_tq.ml);
+        while (!ct_tq.exitFlag && (NULL == ct_tq.head))
+            PR_WaitCondVar(ct_tq.new_op, PR_INTERVAL_NO_TIMEOUT);
+        pollingListNeeded = ct_tq.op_count;
+        PR_Unlock(ct_tq.ml);
+
+        /* Okay. We're history */
+        if (ct_tq.exitFlag) break;
+
+    /*
+     * We are not holding the ct_tq.ml lock now, so more items may
+     * get added to ct_tq during this window of time.  We hope
+     * that 10 more spaces in the polling list should be enough.
+     */
+        pollingListNeeded += 10;
+        if (pollingListNeeded > pollingSlotsAllocated)
+        {
+            if (NULL != pollingList) PR_DELETE(pollingList);
+            pollingList = PR_MALLOC(pollingListNeeded * sizeof(struct pollfd));
+            PR_ASSERT(NULL != pollingList);
+            pollingSlotsAllocated = pollingListNeeded;
+        }
+
+#if defined(DEBUG)
+        if (pollingListNeeded > ct_debug.pollingListMax)
+            ct_debug.pollingListMax = pollingListNeeded;
+#endif
+
+        /*
+         * Build up a polling list.
+         * This list is sorted on time. Operations that have been
+         * interrupted are completed and not included in the list.
+         * There is an assertion that the operation is in progress.
+         */
+        pollingListUsed = 0;
+        PR_Lock(ct_tq.ml);
+
+        for (op = ct_tq.head; NULL != op;)
+        {
+            if (ct_continuation_abort == op->status)
+            {
+                op->result.code = -1;
+                op->syserrno = EINTR;
+                op = ct_FinishTimedInternal(op);
+            }
+            else
+            {
+                if (pollingListUsed == pollingSlotsAllocated) break;
+                PR_ASSERT(ct_continuation_done != op->status);
+                op->status = ct_continuation_inprogress;
+                pollingList[pollingListUsed].revents = 0;
+                pollingList[pollingListUsed].fd = op->arg1.osfd;
+                pollingList[pollingListUsed].events = op->event;
+                pollingListUsed += 1;
+                op = op->next;
+            }
+        }
+
+        PR_Unlock(ct_tq.ml);
+
+        /*
+         * If 'op' isn't NULL at this point, then we didn't get to
+         * the end of the list. That means that more items got added
+         * to the list than we anticipated. So, forget this iteration,
+         * go around the horn again.
+         * One would hope this doesn't happen all that often.
+         */
+        if (NULL != op)
+        {
+#if defined(DEBUG)
+            ct_debug.predictionsFoiled += 1;  /* keep track */
+#endif
+            continue;  /* make it rethink things */
+        }
+
+        if (NULL == ct_tq.head) continue;  /* did list evaporate? */
+
+        /*
+         * We don't want to wait forever on this poll. So keep
+         * the interval down. The operations, if they are timed,
+         * still have to timeout, while those that are not timed
+         * should persist forever. But they may be aborted. That's
+         * what this anxiety is all about.
+         */
+        if (PR_INTERVAL_NO_TIMEOUT == ct_tq.head->timeout)
+            msecs = CT_DEFAULT_POLL_MSEC;
+        else
+        {
+            timeout = ct_tq.head->absolute - PR_IntervalNow();
+            if (timeout <= 0) msecs = 0;  /* already timed out */
+            else if (timeout >= mx_poll_ticks) msecs = CT_DEFAULT_POLL_MSEC;
+            else msecs = (PRInt32)PR_IntervalToMilliseconds(timeout);
+    }
+
+        rv = poll(pollingList, pollingListUsed, msecs);
+        
+        if ((-1 == rv) && ((errno == EINTR) || (errno == EAGAIN)))
+            continue; /* go around the loop again */
+
+    if (rv > 0)
+    {
+            /*
+             * poll() says that something in our list is ready for some more
+             * action. Find it, load up the operation and see what happens.
+             */
+
+            /*
+             * This may work out okay. The rule is that only this thread,
+             * the continuation thread, can remove elements from the list.
+             * Therefore, the list is at worst, longer than when we built
+             * the polling list.
+             */
+
+            op = ct_tq.head;
+            for (pollIndex = 0; pollIndex < pollingListUsed; ++pollIndex)
+            {
+                PR_ASSERT(NULL != op);
+                if (0 != pollingList[pollIndex].revents)
+                {
+                    /*
+                     * This one wants attention. Redo the operation.
+                     * We know that there can only be more elements
+                     * in the op list than we knew about when we created
+                     * the poll list. Therefore, we might have to skip
+                     * a few ops to find the right one to operate on.
+                     */
+                    while ((pollingList[pollIndex].fd != op->arg1.osfd)
+                    || (pollingList[pollIndex].events != op->event))
+                    {
+                        PR_ASSERT(NULL != op->next);  /* it has to be in there */
+                        op = op->next;  /* keep advancing down the list */
+                    }
+
+                    /*
+                     * Skip over all those not in progress. They'll be
+                     * pruned next time we build a polling list. Call
+                     * the continuation function. If it reports completion,
+                     * finish off the operation.
+                     */
+                    if ((ct_continuation_inprogress == op->status)
+                    && (op->function(op, pollingList[pollIndex].revents)))
+                    {
+                        PR_Lock(ct_tq.ml);
+                        op = ct_FinishTimedInternal(op);
+                        PR_Unlock(ct_tq.ml);
+                    }
+                    continue;
+                }
+                op = op->next;  /* progress to next operation */
+            }
+        }
+
+        /*
+         * This is timeout processing. It is done after checking
+         * for good completions. Those that just made it under the
+         * wire are lucky, but none the less, valid.
+         */
+        if ((NULL != ct_tq.head)
+        && (PR_INTERVAL_NO_TIMEOUT != ct_tq.head->timeout))
+        {
+            now = PR_IntervalNow();
+            while ((PRInt32)(ct_tq.head->absolute - now) <= 0)
+            {
+                /* 
+                 * The leading element of the timed queue has timed
+                 * out. Get rid of it. In any case go around the
+                 * loop again, computing the polling list, checking
+                 * for interrupted operations.
+                 */
+
+                PR_Lock(ct_tq.ml);
+                ct_tq.head->result.code = -1;
+                ct_tq.head->syserrno = ETIMEDOUT;
+                (void)ct_FinishTimedInternal(ct_tq.head);
+                PR_Unlock(ct_tq.ml);
+                if ((NULL == ct_tq.head)
+                || (PR_INTERVAL_NO_TIMEOUT == ct_tq.head->timeout))
+                    break;
+            }
+        }
+    }
+    if (NULL != pollingList) PR_DELETE(pollingList);
+}  /* ContinuationThread */
+
+static PRIntn ct_Continue(ct_Continuation *op)
+{
+    PRStatus rv;
+    /* Finish filling in the blank slots */
+    op->complete = PR_NewCondVar(ct_tq.ml);
+    PR_ASSERT(NULL != op->complete);
+    if (NULL == op->complete)
+    {
+        /*
+        ** A better definition of this error would be nice.
+        ** However, it's unlikely to happen and if it does,
+        ** chances of this being the only place it shows up
+        ** are small.  BTW, this will be an "unknown" error.
+        */
+        op->result.code = -1;
+        op->syserrno = 0;
+        goto finish;
+    }
+
+    op->status = ct_continuation_sumbitted;
+    PR_Lock(ct_tq.ml);  /* we provide the locking */
+
+    ct_InsertTimedInternal(op);  /* insert in the structure */
+
+    PR_NotifyCondVar(ct_tq.new_op);  /* notify the continuation thread */
+
+    while (ct_continuation_done != op->status)  /* wait for completion */
+    {
+        rv = PR_WaitCondVar(op->complete, PR_INTERVAL_NO_TIMEOUT);
+        /*
+         * If we get interrupted, we set state the continuation thread will
+         * see and allow it to finish the I/O operation w/ error. That way
+         * the rule that only the continuation thread is removing elements
+         * from the list is still valid.
+         *
+         * Don't call interrupt on the continuation thread. That'll just
+         * piss him off. He's cycling around at least every mx_poll_ticks
+         * anyhow and should notice the request in there.
+         */
+        if ((PR_FAILURE == rv)
+        && (PR_PENDING_INTERRUPT_ERROR == PR_GetError()))
+            op->status = ct_continuation_abort;  /* our status */
+    }
+
+    PR_Unlock(ct_tq.ml);  /* we provide the locking */
+
+    PR_DestroyCondVar(op->complete);
+
+finish:
+    return op->result.code;  /* and the primary answer */
+}  /* ct_Continue */
+
+/*****************************************************************************/
+/*********************** specific continuation functions *********************/
+/*****************************************************************************/
+static PRBool ct_connect_cont(ct_Continuation *op, PRInt16 revents)
+{
+    op->syserrno = _MD_unix_get_nonblocking_connect_error(op->arg1.osfd);
+    if (op->syserrno != 0) {
+        op->result.code = -1;
+    } else {
+        op->result.code = 0;
+    }
+    return PR_TRUE; /* this one is cooked */
+}  /* ct_connect_cont */
+
+static PRBool ct_accect_cont(ct_Continuation *op, PRInt16 revents)
+{
+    op->syserrno = 0;
+    op->result.code = accept(
+        op->arg1.osfd, op->arg2.buffer, op->arg3.addr_len);
+    if (-1 == op->result.code)
+    {
+        op->syserrno = errno;
+        if (EWOULDBLOCK == errno || EAGAIN == errno)  /* the only thing we allow */
+            return PR_FALSE;  /* do nothing - this one ain't finished */
+    }
+    return PR_TRUE;
+}  /* ct_accect_cont */
+
+static PRBool ct_read_cont(ct_Continuation *op, PRInt16 revents)
+{
+    /*
+     * Any number of bytes will complete the operation. It need
+     * not (and probably will not) satisfy the request. The only
+     * error we continue is EWOULDBLOCK|EAGAIN.
+     */
+    op->result.code = read(
+        op->arg1.osfd, op->arg2.buffer, op->arg3.amount);
+    op->syserrno = errno;
+    return ((-1 == op->result.code) && 
+            (EWOULDBLOCK == op->syserrno || EAGAIN == op->syserrno)) ?
+        PR_FALSE : PR_TRUE;
+}  /* ct_read_cont */
+
+static PRBool ct_recv_cont(ct_Continuation *op, PRInt16 revents)
+{
+    /*
+     * Any number of bytes will complete the operation. It need
+     * not (and probably will not) satisfy the request. The only
+     * error we continue is EWOULDBLOCK|EAGAIN.
+     */
+    op->result.code = recv(
+        op->arg1.osfd, op->arg2.buffer, op->arg3.amount, op->arg4.flags);
+    op->syserrno = errno;
+    return ((-1 == op->result.code) && 
+            (EWOULDBLOCK == op->syserrno || EAGAIN == op->syserrno)) ?
+        PR_FALSE : PR_TRUE;
+}  /* ct_recv_cont */
+
+static PRBool ct_send_cont(ct_Continuation *op, PRInt16 revents)
+{
+    /*
+     * We want to write the entire amount out, no matter how many
+     * tries it takes. Keep advancing the buffer and the decrementing
+     * the amount until the amount goes away. Return the total bytes
+     * (which should be the original amount) when finished (or an
+     * error).
+     */
+    PRIntn bytes = send(
+        op->arg1.osfd, op->arg2.buffer, op->arg3.amount, op->arg4.flags);
+    op->syserrno = errno;
+    if (bytes > 0)  /* this is progress */
+    {
+        char *bp = op->arg2.buffer;
+        bp += bytes;  /* adjust the buffer pointer */
+        op->arg2.buffer = bp;
+        op->result.code += bytes;  /* accumulate the number sent */
+        op->arg3.amount -= bytes;  /* and reduce the required count */
+        return (0 == op->arg3.amount) ? PR_TRUE : PR_FALSE;
+    }
+    else return ((-1 == bytes) &&
+        (EWOULDBLOCK == op->syserrno || EAGAIN == op->syserrno)) ? 
+        PR_FALSE : PR_TRUE;
+}  /* ct_send_cont */
+
+static PRBool ct_write_cont(ct_Continuation *op, PRInt16 revents)
+{
+    /*
+     * We want to write the entire amount out, no matter how many
+     * tries it takes. Keep advancing the buffer and the decrementing
+     * the amount until the amount goes away. Return the total bytes
+     * (which should be the original amount) when finished (or an
+     * error).
+     */
+    PRIntn bytes = write(
+        op->arg1.osfd, op->arg2.buffer, op->arg3.amount);
+    op->syserrno = errno;
+    if (bytes > 0)  /* this is progress */
+    {
+        char *bp = op->arg2.buffer;
+        bp += bytes;  /* adjust the buffer pointer */
+        op->arg2.buffer = bp;
+        op->result.code += bytes;  /* accumulate the number sent */
+        op->arg3.amount -= bytes;  /* and reduce the required count */
+        return (0 == op->arg3.amount) ? PR_TRUE : PR_FALSE;
+    }
+    else return ((-1 == bytes) &&
+        (EWOULDBLOCK == op->syserrno || EAGAIN == op->syserrno)) ? 
+        PR_FALSE : PR_TRUE;
+}  /* ct_write_cont */
+
+static PRBool ct_writev_cont(ct_Continuation *op, PRInt16 revents)
+{
+    /*
+     * Same rules as write, but continuing seems to be a bit more
+     * complicated. As the number of bytes sent grows, we have to
+     * redefine the vector we're pointing at. We might have to
+     * modify an individual vector parms or we might have to eliminate
+     * a pair altogether.
+     */
+    PRIntn bytes = writev(
+        op->arg1.osfd, op->arg2.buffer, op->arg3.amount);
+    op->syserrno = errno;
+    if (bytes > 0)  /* this is progress */
+    {
+        PRIntn iov_index;
+        struct iovec *iov = op->arg2.buffer;
+    op->result.code += bytes;  /* accumulate the number sent */
+        for (iov_index = 0; iov_index < op->arg3.amount; ++iov_index)
+        {
+            /* how much progress did we make in the i/o vector? */
+            if (bytes < iov[iov_index].iov_len)
+            {
+                /* this element's not done yet */
+                char **bp = (char**)&(iov[iov_index].iov_base);
+                iov[iov_index].iov_len -= bytes;  /* there's that much left */
+                *bp += bytes;  /* starting there */
+                break;  /* go off and do that */
+            }
+            bytes -= iov[iov_index].iov_len;  /* that element's consumed */
+        }
+        op->arg2.buffer = &iov[iov_index];  /* new start of array */
+        op->arg3.amount -= iov_index;  /* and array length */
+        return (0 == op->arg3.amount) ? PR_TRUE : PR_FALSE;
+    }
+    else return ((-1 == bytes) &&
+        (EWOULDBLOCK == op->syserrno || EAGAIN == op->syserrno)) ? 
+        PR_FALSE : PR_TRUE;
+}  /* ct_writev_cont */
+
+static PRBool ct_sendto_cont(ct_Continuation *op, PRInt16 revents)
+{
+    PRIntn bytes = sendto(
+        op->arg1.osfd, op->arg2.buffer, op->arg3.amount, op->arg4.flags,
+        (struct sockaddr*)op->arg5.addr, PR_NETADDR_SIZE(op->arg5.addr));
+    op->syserrno = errno;
+    if (bytes > 0)  /* this is progress */
+    {
+        char *bp = op->arg2.buffer;
+        bp += bytes;  /* adjust the buffer pointer */
+        op->arg2.buffer = bp;
+        op->result.code += bytes;  /* accumulate the number sent */
+        op->arg3.amount -= bytes;  /* and reduce the required count */
+        return (0 == op->arg3.amount) ? PR_TRUE : PR_FALSE;
+    }
+    else return ((-1 == bytes) && 
+        (EWOULDBLOCK == op->syserrno || EAGAIN == op->syserrno)) ?
+    PR_FALSE : PR_TRUE;
+}  /* ct_sendto_cont */
+
+static PRBool ct_recvfrom_cont(ct_Continuation *op, PRInt16 revents)
+{
+    _PRSize_t addr_len = sizeof(PRNetAddr);
+    op->result.code = recvfrom(
+        op->arg1.osfd, op->arg2.buffer, op->arg3.amount,
+        op->arg4.flags, (struct sockaddr*)op->arg5.addr, &addr_len);
+    op->syserrno = errno;
+    return ((-1 == op->result.code) && 
+            (EWOULDBLOCK == op->syserrno || EAGAIN == op->syserrno)) ?
+        PR_FALSE : PR_TRUE;
+}  /* ct_recvfrom_cont */
+
+static PRIntn _ct_MapOptionName(
+    PRSockOption optname, PRInt32 *level, PRInt32 *name)
+{
+    static PRInt32 socketOptions[PR_SockOpt_Last] =
+    {
+        0, SO_LINGER, SO_REUSEADDR, SO_KEEPALIVE, SO_RCVBUF, SO_SNDBUF,
+        IP_TTL, IP_TOS, IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP,
+        IP_MULTICAST_IF, IP_MULTICAST_TTL, IP_MULTICAST_LOOP,
+        TCP_NODELAY, TCP_MAXSEG
+    };
+    static PRInt32 socketLevels[PR_SockOpt_Last] =
+    {
+        0, SOL_SOCKET, SOL_SOCKET, SOL_SOCKET, SOL_SOCKET, SOL_SOCKET,
+        IPPROTO_IP, IPPROTO_IP, IPPROTO_IP, IPPROTO_IP,
+        IPPROTO_IP, IPPROTO_IP, IPPROTO_IP,
+        IPPROTO_TCP, IPPROTO_TCP
+    };
+
+    if ((optname < PR_SockOpt_Linger)
+    && (optname > PR_SockOpt_MaxSegment))
+    {
+        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
+        return -1;
+    }
+
+    *name = socketOptions[optname];
+    *level = socketLevels[optname];
+    return 0;
+}  /* _ct_MapOptionName */
+
+PR_IMPLEMENT(void) _PR_InitIO()
+{
+    PRIntn rv;
+    _pr_stdin = ct_SetMethods(0, PR_DESC_FILE);
+    _pr_stdout = ct_SetMethods(1, PR_DESC_FILE);
+    _pr_stderr = ct_SetMethods(2, PR_DESC_FILE);
+
+    PR_ASSERT(_pr_stdin && _pr_stdout && _pr_stderr);
+
+    ct_tq.op_count = 0;
+    ct_tq.head = ct_tq.tail = NULL;
+    ct_tq.ml = PR_NewLock();
+    PR_ASSERT(NULL != ct_tq.ml);
+    ct_tq.new_op = PR_NewCondVar(ct_tq.ml);
+    PR_ASSERT(NULL != ct_tq.new_op);
+    ct_tq.op = NULL;
+    ct_tq.exitFlag = PR_FALSE;
+#if defined(DEBUG)
+    memset(&ct_debug, 0, sizeof(struct ct_debug_s));
+#endif
+
+    ct_tq.thread = PR_CreateThread(
+        PR_SYSTEM_THREAD, ContinuationThread, NULL,
+        PR_PRIORITY_URGENT, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
+
+    PR_ASSERT(NULL != ct_tq.thread);
+
+    _pr_flock_lock = PR_NewLock();
+    PR_ASSERT(NULL != _pr_flock_lock);
+    _pr_rename_lock = PR_NewLock();
+    PR_ASSERT(NULL != _pr_rename_lock);
+}  /* _PR_InitIO */
+
+PR_IMPLEMENT(PRFileDesc*) PR_GetSpecialFD(PRSpecialFD osfd)
+{
+    PRFileDesc *result = NULL;
+    PR_ASSERT(osfd >= PR_StandardInput && osfd <= PR_StandardError);
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+    
+    switch (osfd)
+    {
+        case PR_StandardInput: result = _pr_stdin; break;
+        case PR_StandardOutput: result = _pr_stdout; break;
+        case PR_StandardError: result = _pr_stderr; break;
+        default:
+            (void)PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
+    }
+    return result;
+}  /* PR_GetSpecialFD */
+
+/*****************************************************************************/
+/***************************** I/O private methods ***************************/
+/*****************************************************************************/
+
+static PRBool ct_TestAbort(void)
+{
+    PRThread *me = PR_CurrentThread();
+    if(me->state & CT_THREAD_ABORTED)
+    {
+        PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
+        me->state &= ~CT_THREAD_ABORTED;
+        return PR_TRUE;
+    }
+    return PR_FALSE;
+}  /* ct_TestAbort */
+
+static void ct_MapError(void (*mapper)(PRIntn), PRIntn syserrno)
+{
+    switch (syserrno)
+    {
+        case EINTR:
+            PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0); break;
+        case ETIMEDOUT:
+            PR_SetError(PR_IO_TIMEOUT_ERROR, 0); break;
+        default:
+            mapper(syserrno);
+    }
+}  /* ct_MapError */
+
+static PRStatus ct_Close(PRFileDesc *fd)
+{
+    PRIntn syserrno, rv = 0;
+    if ((NULL == fd) || (NULL == fd->secret))
+    {
+        PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
+        return PR_FAILURE;
+    }
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    if (_PR_FILEDESC_OPEN == fd->secret->state)
+    {
+        rv = close(fd->secret->md.osfd);
+        syserrno = errno;
+    }
+
+    PR_DELETE(fd->secret);
+    PR_DELETE(fd);
+    if (-1 == rv)
+    {
+        ct_MapError(_PR_MD_MAP_CLOSE_ERROR, syserrno);
+        return PR_FAILURE;
+    }
+    return PR_SUCCESS;
+}  /* ct_Close */
+
+static PRInt32 ct_Read(PRFileDesc *fd, void *buf, PRInt32 amount)
+{
+    PRInt32 syserrno, bytes = -1;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    bytes = read(fd->secret->md.osfd, buf, amount);
+    syserrno = errno;
+
+    if ((bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
+        && (!fd->secret->nonblocking))
+    {
+        ct_Continuation op;
+        op.arg1.osfd = fd->secret->md.osfd;
+        op.arg2.buffer = buf;
+        op.arg3.amount = amount;
+        op.timeout = PR_INTERVAL_NO_TIMEOUT;
+        op.function = ct_read_cont;
+        op.event = POLLIN | POLLPRI;
+        bytes = ct_Continue(&op);
+        syserrno = op.syserrno;
+    }
+    if (bytes < 0)
+        ct_MapError(_PR_MD_MAP_READ_ERROR, syserrno);
+    return bytes;
+}  /* ct_Read */
+
+static PRInt32 ct_Write(PRFileDesc *fd, const void *buf, PRInt32 amount)
+{
+    PRInt32 syserrno, bytes = -1;
+    PRBool fNeedContinue = PR_FALSE;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    bytes = write(fd->secret->md.osfd, buf, amount);
+    syserrno = errno;
+
+    if ( (bytes >= 0) && (bytes < amount) && (!fd->secret->nonblocking) )
+    {
+        buf = (char *) buf + bytes;
+        amount -= bytes;
+        fNeedContinue = PR_TRUE;
+    }
+    if ( (bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
+        && (!fd->secret->nonblocking) )
+    {
+        bytes = 0;
+        fNeedContinue = PR_TRUE;
+    }
+
+    if (fNeedContinue == PR_TRUE)
+    {
+        ct_Continuation op;
+        op.arg1.osfd = fd->secret->md.osfd;
+        op.arg2.buffer = (void*)buf;
+        op.arg3.amount = amount;
+        op.timeout = PR_INTERVAL_NO_TIMEOUT;
+        op.result.code = bytes;  /* initialize the number sent */
+        op.function = ct_write_cont;
+        op.event = POLLOUT | POLLPRI;
+        bytes = ct_Continue(&op);
+        syserrno = op.syserrno;
+    }
+    if (bytes == -1)
+        ct_MapError(_PR_MD_MAP_WRITE_ERROR, syserrno);
+    return bytes;
+}  /* ct_Write */
+
+static PRInt32 ct_Writev(
+    PRFileDesc *fd, PRIOVec *iov, PRInt32 iov_len, PRIntervalTime timeout)
+{
+    PRIntn iov_index = 0;
+    PRBool fNeedContinue = PR_FALSE;
+    PRInt32 syserrno, bytes = -1, rv = -1;
+
+    if (ct_TestAbort()) return rv;
+
+    /*
+     * The first shot at this can use the client's iov directly.
+     * Only if we have to continue the operation do we have to
+     * make a copy that we can modify.
+     */
+    rv = bytes = writev(fd->secret->md.osfd, (struct iovec*)iov, iov_len);
+    syserrno = errno;
+
+    /*
+     * If we moved some bytes (ie., bytes > 0) how does that implicate
+     * the i/o vector list. In other words, exactly where are we within
+     * that array? What are the parameters for resumption? Maybe we're
+     * done!
+     */
+    if ((bytes > 0) && (!fd->secret->nonblocking))
+    {
+        for (iov_index = 0; iov_index < iov_len; ++iov_index)
+        {
+            if (bytes < iov[iov_index].iov_len) break; /* continue w/ what's left */
+            bytes -= iov[iov_index].iov_len;  /* this one's done cooked */
+        }
+    }
+
+    if ((bytes >= 0) && (iov_index < iov_len) && (!fd->secret->nonblocking))
+    {
+        if (PR_INTERVAL_NO_WAIT == timeout)
+        {
+            rv = -1;
+            syserrno = ETIMEDOUT;
+        }
+        else fNeedContinue = PR_TRUE;
+    }
+    else if ((bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
+        && (!fd->secret->nonblocking))
+    {
+        if (PR_INTERVAL_NO_WAIT == timeout) syserrno = ETIMEDOUT;
+        else
+        {
+            rv = bytes = 0;
+            fNeedContinue = PR_TRUE;
+        }
+    }
+
+    if (fNeedContinue == PR_TRUE)
+    {
+        ct_Continuation op;
+        /*
+         * Okay. Now we need a modifiable copy of the array.
+         * Allocate some storage and copy the (already) modified
+         * bits into the new vector. The is copying only the
+         * part of the array that's still valid. The array may
+         * have a new length and the first element of the array may
+         * have different values.
+         */
+        struct iovec *osiov = NULL, *tiov;
+        PRIntn osiov_len = iov_len - iov_index;  /* recompute */
+        osiov = PR_MALLOC(osiov_len * sizeof(struct iovec));
+        PR_ASSERT(NULL != osiov);
+        for (tiov = osiov; iov_index < iov_len; ++iov_index)
+        {
+            tiov->iov_base = iov[iov_index].iov_base;
+            tiov->iov_len = iov[iov_index].iov_len;
+            tiov += 1;
+        }
+        osiov->iov_len -= bytes;  /* that may be partially done */
+        /* so advance the description */
+        osiov->iov_base = (char*)osiov->iov_base + bytes;
+
+        op.arg1.osfd = fd->secret->md.osfd;
+        op.arg2.buffer = (void*)osiov;
+        op.arg3.amount = osiov_len;
+        op.timeout = timeout;
+        op.result.code = rv;
+        op.function = ct_writev_cont;
+        op.event = POLLOUT | POLLPRI;
+        rv = ct_Continue(&op);
+        syserrno = op.syserrno;
+        PR_DELETE(osiov);
+    }
+    if (rv == -1) ct_MapError(_PR_MD_MAP_WRITEV_ERROR, syserrno);
+    return rv;
+}  /* ct_Writev */
+
+static PRInt32 ct_Seek(PRFileDesc *fd, PRInt32 offset, PRSeekWhence whence)
+{
+    PRIntn how;
+    off_t pos = -1;
+    
+    if (ct_TestAbort()) return pos;
+
+    switch (whence)
+    {
+        case PR_SEEK_SET: how = SEEK_SET; break;
+        case PR_SEEK_CUR: how = SEEK_CUR; break;
+        case PR_SEEK_END: how = SEEK_END; break;
+        default:
+            PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
+            return pos;
+    }
+    pos = lseek(fd->secret->md.osfd, offset, how);
+    if (pos == -1)
+        ct_MapError(_PR_MD_MAP_LSEEK_ERROR, errno);
+    return pos;
+}  /* ct_Seek */
+
+static PRInt64 ct_Seek64(PRFileDesc *fd, PRInt64 offset, PRSeekWhence whence)
+{
+    PRInt64 on;
+    PRInt32 off, position = -1;
+    LL_L2I(off, offset);  /* possible loss of bits */
+    LL_I2L(on, off);  /* get back original or notice we didn't */
+    if (LL_EQ(on, offset)) position = ct_Seek(fd, off, whence);
+    LL_I2L(on, position);  /* might not have worked */
+    return on;
+}  /* ct_Seek64 */
+
+static PRInt32 ct_Available(PRFileDesc *fd)
+{
+    PRInt32 rv, bytes = -1;
+    if (ct_TestAbort()) return bytes;
+
+    rv = ioctl(fd->secret->md.osfd, FIONREAD, &bytes);
+
+    if (rv == -1)
+        ct_MapError(_PR_MD_MAP_SOCKETAVAILABLE_ERROR, errno);
+    return bytes;
+}  /* ct_Available */
+
+static PRInt64 ct_Available64(PRFileDesc *fd)
+{
+    PRInt64 rv;
+    PRInt32 avail = ct_Available(fd);
+    LL_I2L(rv, avail);
+    return rv;
+}  /* ct_Available64 */
+
+static PRStatus ct_Synch(PRFileDesc *fd)
+{
+    return PR_SUCCESS;
+} /* ct_Synch */
+
+static PRStatus ct_FileInfo(PRFileDesc *fd, PRFileInfo *info)
+{
+    PRInt32 rv;
+    struct stat sb;
+    PRInt64 s, s2us;
+ 
+    if ((rv = fstat(fd->secret->md.osfd, &sb)) == 0 )
+    {
+        if (info)
+        {
+            if (S_IFREG & sb.st_mode)
+                info->type = PR_FILE_FILE ;
+            else if (S_IFDIR & sb.st_mode)
+                info->type = PR_FILE_DIRECTORY;
+            else
+                info->type = PR_FILE_OTHER;
+            info->size = sb.st_size;
+#if defined(IRIX) && defined(HAVE_LONG_LONG)
+            info->modifyTime = (PR_USEC_PER_SEC * (PRInt64)sb.st_mtim.tv_sec);
+            info->creationTime = (PR_USEC_PER_SEC * (PRInt64)sb.st_ctim.tv_sec);
+            info->modifyTime = (PR_USEC_PER_SEC * (PRInt64)sb.st_mtime);
+            info->creationTime = (PR_USEC_PER_SEC * (PRInt64)sb.st_ctime);
+#else
+            LL_I2L(s, sb.st_mtime);
+            LL_I2L(s2us, PR_USEC_PER_SEC);
+            LL_MUL(s, s, s2us);
+            info->modifyTime = s;
+            LL_I2L(s, sb.st_ctime);
+            LL_MUL(s, s, s2us);
+            info->creationTime = s;
+#endif
+        }
+    }
+    return (0 == rv) ? PR_SUCCESS : PR_FAILURE;
+}  /* ct_FileInfo */
+
+static PRStatus ct_FileInfo64(PRFileDesc *fd, PRFileInfo64 *info)
+{
+    PRFileInfo info32;
+    PRStatus rv = ct_FileInfo(fd, &info32);
+    if (PR_SUCCESS == rv)
+    {
+        info->type = info32.type;
+        info->creationTime = info32.creationTime;
+        info->modifyTime = info32.modifyTime;
+        LL_I2L(info->size, info32.size);
+    }
+    return rv;
+}  /* ct_FileInfo64 */
+
+static PRStatus ct_Fsync(PRFileDesc *fd)
+{
+    PRIntn rv = -1;
+    PRIntn syserrno = ct_TestAbort();
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    rv = fsync(fd->secret->md.osfd);
+    if (rv < 0) {
+        ct_MapError(_PR_MD_MAP_FSYNC_ERROR, errno);
+        return PR_FAILURE;
+    }
+    return PR_SUCCESS;
+}  /* ct_Fsync */
+
+static PRStatus ct_Connect(
+    PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout)
+{
+    PRIntn rv = -1, syserrno;
+    PRSize addr_len = PR_NETADDR_SIZE(addr);
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    rv = connect(fd->secret->md.osfd, (struct sockaddr*)addr, addr_len);
+    syserrno = errno;
+    if ((-1 == rv) && (EINPROGRESS == syserrno) && (!fd->secret->nonblocking))
+    {
+        if (PR_INTERVAL_NO_WAIT == timeout) syserrno = ETIMEDOUT;
+        else
+        {
+            ct_Continuation op;
+            op.arg1.osfd = fd->secret->md.osfd;
+            op.arg2.buffer = (void*)addr;
+            op.arg3.amount = addr_len;
+            op.timeout = timeout;
+            op.function = ct_connect_cont;
+            op.event = POLLOUT | POLLPRI;
+            rv = ct_Continue(&op);
+            syserrno = op.syserrno;
+        }
+    }
+    if (-1 == rv) {
+        ct_MapError(_PR_MD_MAP_CONNECT_ERROR, syserrno);
+        return PR_FAILURE;
+    }
+    return PR_SUCCESS;
+}  /* ct_Connect */
+
+PR_IMPLEMENT(PRStatus) PR_GetConnectStatus(const PRPollDesc *pd)
+{
+    PRInt32 osfd;
+    PRFileDesc *bottom = pd->fd;
+    int err, len;
+
+    while (bottom->lower != NULL) {
+        bottom = bottom->lower;
+    }
+    osfd = bottom->secret->md.osfd;
+
+    if ((pd->out_flags & (PR_POLL_WRITE | PR_POLL_EXCEPT)) == 0) {
+        PR_SetError(PR_IN_PROGRESS_ERROR, 0);
+        return PR_FAILURE;
+    }
+
+    err = _MD_unix_get_nonblocking_connect_error(osfd);
+    if (err != 0) {
+        _PR_MD_MAP_CONNECT_ERROR(err);
+        return PR_FAILURE;
+    }
+    return PR_SUCCESS;
+}
+
+static PRFileDesc* ct_Accept(
+    PRFileDesc *fd, PRNetAddr *addr, PRIntervalTime timeout)
+{
+    PRFileDesc *newfd = NULL;
+    PRIntn syserrno, osfd = -1;
+    _PRSize_t addr_len = sizeof(PRNetAddr);
+
+    if (ct_TestAbort()) return newfd;
+
+    osfd = accept(fd->secret->md.osfd, (struct sockaddr*)addr, &addr_len);
+    syserrno = errno;
+
+    if (osfd == -1)
+    {
+        if (fd->secret->nonblocking) goto failed;
+
+        if (EWOULDBLOCK != syserrno && EAGAIN != syserrno) goto failed;
+        else
+        {
+            if (PR_INTERVAL_NO_WAIT == timeout) syserrno = ETIMEDOUT;
+            else
+            {
+                ct_Continuation op;
+                op.arg1.osfd = fd->secret->md.osfd;
+                op.arg2.buffer = addr;
+                op.arg3.addr_len = &addr_len;
+                op.timeout = timeout;
+                op.function = ct_accect_cont;
+                op.event = POLLIN | POLLPRI;
+                osfd = ct_Continue(&op);
+                syserrno = op.syserrno;
+            }
+            if (osfd < 0) goto failed;
+        }
+    }
+#ifdef AIX
+    /* mask off the first byte of struct sockaddr (the length field) */
+    if (addr)
+    addr->inet.family &= 0x00ff;
+#endif
+    newfd = ct_SetMethods(osfd, PR_DESC_SOCKET_TCP);
+    if (newfd == NULL)
+    {
+        close(osfd);  /* $$$ whoops! this doesn't work $$$ */
+        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
+    }
+    else
+    {
+        newfd->secret->state = _PR_FILEDESC_OPEN;
+        PR_ASSERT((NULL == addr) || (PR_NETADDR_SIZE(addr) == addr_len));
+#if defined(_PR_INET6)
+        PR_ASSERT((NULL == addr) || (addr->raw.family == PF_INET)
+                || (addr->raw.family == PF_INET6));
+#else
+        PR_ASSERT((NULL == addr) || (addr->raw.family == PF_INET));
+#endif
+    }
+    return newfd;
+
+failed:
+    ct_MapError(_PR_MD_MAP_ACCEPT_ERROR, syserrno);
+    return NULL;
+}  /* ct_Accept */
+
+static PRStatus ct_Bind(PRFileDesc *fd, const PRNetAddr *addr)
+{
+    PRIntn rv;
+    PRInt32 one = 1;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+#if defined(_PR_INET6)
+    PR_ASSERT(addr->raw.family == PF_INET || addr->raw.family == PF_INET6);
+#else
+    PR_ASSERT(addr->raw.family == PF_INET);
+#endif
+
+    rv = setsockopt(
+        fd->secret->md.osfd, SOL_SOCKET, SO_REUSEADDR,
+        (_PRSockOptVal_t) &one, sizeof(one));
+    if (rv == -1) {
+        ct_MapError(_PR_MD_MAP_SETSOCKOPT_ERROR, errno);
+        return PR_FAILURE;
+    }
+
+    rv = bind(fd->secret->md.osfd, (struct sockaddr*)addr, PR_NETADDR_SIZE(addr));
+
+    if (rv == -1) {
+        ct_MapError(_PR_MD_MAP_BIND_ERROR, errno);
+        return PR_FAILURE;
+    }
+    fd->secret->state = _PR_FILEDESC_OPEN;
+    return PR_SUCCESS;
+}  /* ct_Bind */
+
+static PRStatus ct_Listen(PRFileDesc *fd, PRIntn backlog)
+{
+    PRIntn rv;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    rv = listen(fd->secret->md.osfd, backlog);
+    if (rv == -1) {
+        ct_MapError(_PR_MD_MAP_LISTEN_ERROR, errno);
+        return PR_FAILURE;
+    }
+    return PR_SUCCESS;
+}  /* ct_Listen */
+
+static PRStatus ct_Shutdown(PRFileDesc *fd, PRIntn how)
+{
+    PRIntn rv = -1;
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    rv = shutdown(fd->secret->md.osfd, how);
+
+    if (rv == -1) {
+        ct_MapError(_PR_MD_MAP_SHUTDOWN_ERROR, errno);
+        return PR_FAILURE;
+    }
+    return PR_SUCCESS;
+}  /* ct_Shutdown */
+
+static PRInt32 ct_Recv(
+    PRFileDesc *fd, void *buf, PRInt32 amount,
+    PRIntn flags, PRIntervalTime timeout)
+{
+    PRInt32 syserrno, bytes = -1;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    bytes = recv(fd->secret->md.osfd, buf, amount, flags);
+    syserrno = errno;
+
+    if ((bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
+        && (!fd->secret->nonblocking))
+    {
+        if (PR_INTERVAL_NO_WAIT == timeout) syserrno = ETIMEDOUT;
+        else
+        {
+            ct_Continuation op;
+            op.arg1.osfd = fd->secret->md.osfd;
+            op.arg2.buffer = buf;
+            op.arg3.amount = amount;
+            op.arg4.flags = flags;
+            op.timeout = timeout;
+            op.function = ct_recv_cont;
+            op.event = POLLIN | POLLPRI;
+            bytes = ct_Continue(&op);
+            syserrno = op.syserrno;
+        }
+    }
+    if (bytes < 0)
+        ct_MapError(_PR_MD_MAP_RECV_ERROR, syserrno);
+    return bytes;
+}  /* ct_Recv */
+
+static PRInt32 ct_Send(
+    PRFileDesc *fd, const void *buf, PRInt32 amount,
+    PRIntn flags, PRIntervalTime timeout)
+{
+    PRInt32 syserrno, bytes = -1;
+    PRBool fNeedContinue = PR_FALSE;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    bytes = send(fd->secret->md.osfd, buf, amount, flags);
+    syserrno = errno;
+
+    if ( (bytes >= 0) && (bytes < amount) && (!fd->secret->nonblocking) )
+    {
+        if (PR_INTERVAL_NO_WAIT == timeout)
+        {
+            bytes = -1;
+            syserrno = ETIMEDOUT;
+        }
+        else
+        {
+            buf = (char *) buf + bytes;
+            amount -= bytes;
+            fNeedContinue = PR_TRUE;
+        }
+    }
+    if ( (bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
+        && (!fd->secret->nonblocking) )
+    {
+        if (PR_INTERVAL_NO_WAIT == timeout) syserrno = ETIMEDOUT;
+        else
+        {
+            bytes = 0;
+            fNeedContinue = PR_TRUE;
+        }
+    }
+
+    if (fNeedContinue == PR_TRUE)
+    {
+        ct_Continuation op;
+        op.arg1.osfd = fd->secret->md.osfd;
+        op.arg2.buffer = (void*)buf;
+        op.arg3.amount = amount;
+        op.arg4.flags = flags;
+        op.timeout = timeout;
+        op.result.code = bytes;  /* initialize the number sent */
+        op.function = ct_send_cont;
+        op.event = POLLOUT | POLLPRI;
+        bytes = ct_Continue(&op);
+        syserrno = op.syserrno;
+    }
+    if (bytes == -1)
+        ct_MapError(_PR_MD_MAP_SEND_ERROR, syserrno);
+    return bytes;
+}  /* ct_Send */
+
+static PRInt32 ct_SendTo(
+    PRFileDesc *fd, const void *buf,
+    PRInt32 amount, PRIntn flags, const PRNetAddr *addr,
+    PRIntervalTime timeout)
+{
+    PRInt32 syserrno, bytes = -1;
+    PRBool fNeedContinue = PR_FALSE;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+#if defined(_PR_INET6)
+    PR_ASSERT(addr->raw.family == PF_INET || addr->raw.family == PF_INET6);
+#else
+	PR_ASSERT(addr->raw.family == PF_INET);
+#endif
+    bytes = sendto(
+        fd->secret->md.osfd, buf, amount, flags,
+        (struct sockaddr*)addr, PR_NETADDR_SIZE(addr));
+    syserrno = errno;
+    if ( (bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
+        && (!fd->secret->nonblocking) )
+    {
+        if (PR_INTERVAL_NO_WAIT == timeout) syserrno = ETIMEDOUT;
+        else fNeedContinue = PR_TRUE;
+    }
+    if (fNeedContinue == PR_TRUE)
+    {
+        ct_Continuation op;
+        op.arg1.osfd = fd->secret->md.osfd;
+        op.arg2.buffer = (void*)buf;
+        op.arg3.amount = amount;
+        op.arg4.flags = flags;
+        op.arg5.addr = (PRNetAddr*)addr;
+        op.timeout = timeout;
+        op.result.code = 0;  /* initialize the number sent */
+        op.function = ct_sendto_cont;
+        op.event = POLLOUT | POLLPRI;
+        bytes = ct_Continue(&op);
+        syserrno = op.syserrno;
+    }
+    if (bytes < 0)
+        ct_MapError(_PR_MD_MAP_SENDTO_ERROR, syserrno);
+    return bytes;
+}  /* ct_SendTo */
+
+static PRInt32 ct_RecvFrom(PRFileDesc *fd, void *buf, PRInt32 amount,
+    PRIntn flags, PRNetAddr *addr, PRIntervalTime timeout)
+{
+    PRBool fNeedContinue = PR_FALSE;
+    PRInt32 syserrno, bytes = -1, addr_len = sizeof(PRNetAddr);
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    bytes = recvfrom(
+        fd->secret->md.osfd, buf, amount, flags,
+        (struct sockaddr*)addr, &addr_len);
+    syserrno = errno;
+
+    if ( (bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
+        && (!fd->secret->nonblocking) )
+    {
+        if (PR_INTERVAL_NO_WAIT == timeout) syserrno = ETIMEDOUT;
+        else fNeedContinue = PR_TRUE;
+    }
+
+    if (fNeedContinue == PR_TRUE)
+    {
+        ct_Continuation op;
+        op.arg1.osfd = fd->secret->md.osfd;
+        op.arg2.buffer = buf;
+        op.arg3.amount = amount;
+        op.arg4.flags = flags;
+        op.arg5.addr = addr;
+        op.timeout = timeout;
+        op.function = ct_recvfrom_cont;
+        op.event = POLLIN | POLLPRI;
+        bytes = ct_Continue(&op);
+        syserrno = op.syserrno;
+    }
+#ifdef AIX
+    /* mask off the first byte of struct sockaddr (the length field) */
+    if (addr) addr->inet.family &= 0x00ff;
+#endif
+    if (bytes < 0)
+        ct_MapError(_PR_MD_MAP_RECVFROM_ERROR, syserrno);
+    return bytes;
+}  /* ct_RecvFrom */
+
+static PRInt32 ct_TransmitFile(
+    PRFileDesc *sd, PRFileDesc *fd, const void *headers,
+    PRInt32 hlen, PRTransmitFileFlags flags, PRIntervalTime timeout)
+{
+    if (ct_TestAbort()) return -1;
+
+    return _PR_UnixTransmitFile(sd, fd, headers, hlen, flags, timeout);
+}  /* ct_TransmitFile */
+
+/*
+ * XXX: When IPv6 is running, we need to see if this code works
+ * with a PRNetAddr structure that supports both IPv4 and IPv6.
+ */
+static PRInt32 ct_AcceptRead(
+    PRFileDesc *sd, PRFileDesc **nd, PRNetAddr **raddr,
+    void *buf, PRInt32 amount, PRIntervalTime timeout)
+{
+    PRInt32 rv = -1;
+    PRNetAddr remote;
+    PRFileDesc *accepted = NULL;
+    PRIntervalTime start, elapsed;
+
+    if (ct_TestAbort()) return rv;
+
+    if (PR_INTERVAL_NO_TIMEOUT != timeout) start = PR_IntervalNow();
+    if ((accepted = PR_Accept(sd, &remote, timeout)) == NULL) return rv;
+
+    if (PR_INTERVAL_NO_TIMEOUT != timeout)
+    {
+        elapsed = (PRIntervalTime) (PR_IntervalNow() - start);
+        if (elapsed > timeout)
+        {
+            PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
+            goto failed;
+        }
+        else timeout = timeout - elapsed;
+    }
+
+    rv = PR_Recv(accepted, buf, amount, 0, timeout);
+    if (rv >= 0)
+    {
+        /* copy the new info out where caller can see it */
+        *nd = accepted;
+        *raddr = (PRNetAddr *)((char*)buf + amount);
+        memcpy(*raddr, &remote, PR_NETADDR_SIZE(&remote));
+        return rv;
+    }
+
+failed:
+    PR_Close(accepted);
+    return rv;
+}  /* ct_AcceptRead */
+
+static PRStatus ct_GetSockName(PRFileDesc *fd, PRNetAddr *addr)
+{
+    PRIntn rv = -1, addr_len = sizeof(PRNetAddr);
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    rv = getsockname(
+        fd->secret->md.osfd, (struct sockaddr*)addr, &addr_len);
+#ifdef AIX
+    /* mask off the first byte of struct sockaddr (the length field) */
+    if (addr) addr->inet.family &= 0x00ff;
+#endif
+    if (rv == -1) {
+        ct_MapError(_PR_MD_MAP_GETSOCKNAME_ERROR, errno);
+        return PR_FAILURE;
+    } else {
+        PR_ASSERT(addr_len == PR_NETADDR_SIZE(addr));
+#if defined(_PR_INET6)
+        PR_ASSERT(addr->raw.family == PF_INET || addr->raw.family == PF_INET6);
+#else
+	PR_ASSERT(addr->raw.family == PF_INET);
+#endif
+        return PR_SUCCESS;
+    }
+}  /* ct_GetSockName */
+
+static PRStatus ct_GetPeerName(PRFileDesc *fd, PRNetAddr *addr)
+{
+    PRIntn rv = -1, addr_len = sizeof(PRNetAddr);
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    rv = getpeername(
+        fd->secret->md.osfd, (struct sockaddr*)addr, &addr_len);
+
+#ifdef AIX
+    /* mask off the first byte of struct sockaddr (the length field) */
+    if (addr) addr->inet.family &= 0x00ff;
+#endif
+    if (rv == -1) {
+        ct_MapError(_PR_MD_MAP_GETPEERNAME_ERROR, errno);
+        return PR_FAILURE;
+    } else {
+        PR_ASSERT(addr_len == PR_NETADDR_SIZE(addr));
+#if defined(_PR_INET6)
+        PR_ASSERT(addr->raw.family == PF_INET || addr->raw.family == PF_INET6);
+#else
+	PR_ASSERT(addr->raw.family == PF_INET);
+#endif
+        return PR_SUCCESS;
+    }
+}  /* ct_GetPeerName */
+
+static PRStatus ct_GetSockOpt(
+    PRFileDesc *fd, PRSockOption optname, void* optval, PRInt32* optlen)
+{
+    PRIntn rv = -1;
+	PRInt32 result, level, name;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    /*
+     * PR_SockOpt_Nonblocking is a special case that does not
+     * translate to a getsockopt() call.
+     */
+    if (PR_SockOpt_Nonblocking == optname)
+    {
+        PR_ASSERT(sizeof(PRIntn) <= *optlen);
+        *((PRIntn *) optval) = (PRIntn) fd->secret->nonblocking;
+        *optlen = sizeof(PRIntn);
+        return PR_SUCCESS;
+    }
+
+    rv = _ct_MapOptionName(optname, &level, &name);
+    if (0 == rv)
+    {
+        if (PR_SockOpt_Linger == optname)
+        {
+            struct linger linger;
+            int len = sizeof(linger);
+            rv = getsockopt(fd->secret->md.osfd, level, name,
+                (char *) &linger, &len);
+            if (0 == rv)
+            {
+                ((PRLinger*)(optval))->polarity = linger.l_onoff
+                    ? PR_TRUE : PR_FALSE;
+                ((PRLinger*)(optval))->linger = PR_SecondsToInterval(
+                    linger.l_linger);
+                *optlen = sizeof(PRLinger);
+            }
+        }
+        else
+        {
+            rv = getsockopt(fd->secret->md.osfd, level, name,
+                optval, optlen);
+        }
+    }
+
+    if (rv == -1) {
+        ct_MapError(_PR_MD_MAP_GETSOCKOPT_ERROR, errno);
+        return PR_FAILURE;
+    } else {
+        return PR_SUCCESS;
+    }
+}  /* ct_GetSockOpt */
+
+static PRStatus ct_SetSockOpt(
+    PRFileDesc *fd, PRSockOption optname, const void* optval, PRInt32 optlen)
+{
+    PRIntn rv = -1;
+	PRInt32 result, level, name;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    /*
+     * PR_SockOpt_Nonblocking is a special case that does not
+     * translate to a setsockopt() call.
+     */
+    if (PR_SockOpt_Nonblocking == optname)
+    {
+        PR_ASSERT(sizeof(PRIntn) == optlen);
+        fd->secret->nonblocking = *((PRIntn *) optval) ? PR_TRUE
+            : PR_FALSE;
+        return PR_SUCCESS;
+    }
+
+    rv = _ct_MapOptionName(optname, &level, &name);
+    if (0 == rv)
+    {
+        if (PR_SockOpt_Linger == optname)
+        {
+            struct linger linger;
+            linger.l_onoff = ((PRLinger*)(optval))->polarity;
+            linger.l_linger = PR_IntervalToSeconds(
+                ((PRLinger*)(optval))->linger);
+            rv = setsockopt(fd->secret->md.osfd, level, name,
+                (char *) &linger, sizeof(linger));
+        }
+        else
+        {
+            rv = setsockopt(fd->secret->md.osfd, level, name,
+                optval, optlen);
+        }
+    }
+
+    if (rv == -1) {
+        ct_MapError(_PR_MD_MAP_SETSOCKOPT_ERROR, errno);
+        return PR_FAILURE;
+    } else {
+        return PR_SUCCESS;
+    }
+}  /* ct_SetSockOpt */
+
+/*****************************************************************************/
+/****************************** I/O method objects ***************************/
+/*****************************************************************************/
+
+static PRIOMethods _pr_file_methods = {
+    PR_DESC_FILE,
+    ct_Close,
+    ct_Read,
+    ct_Write,
+    ct_Available,
+    ct_Available64,
+    ct_Fsync,
+    ct_Seek,
+    ct_Seek64,
+    ct_FileInfo,
+    ct_FileInfo64,
+    (PRWritevFN)_PR_InvalidInt,		
+    (PRConnectFN)_PR_InvalidStatus,		
+    (PRAcceptFN)_PR_InvalidDesc,		
+    (PRBindFN)_PR_InvalidStatus,		
+    (PRListenFN)_PR_InvalidStatus,		
+    (PRShutdownFN)_PR_InvalidStatus,	
+    (PRRecvFN)_PR_InvalidInt,		
+    (PRSendFN)_PR_InvalidInt,		
+    (PRRecvfromFN)_PR_InvalidInt,	
+    (PRSendtoFN)_PR_InvalidInt,		
+    (PRPollFN)0,         
+    (PRAcceptreadFN)_PR_InvalidInt,   
+    (PRTransmitfileFN)_PR_InvalidInt, 
+    (PRGetsocknameFN)_PR_InvalidStatus,	
+    (PRGetpeernameFN)_PR_InvalidStatus,	
+    (PRGetsockoptFN)_PR_InvalidStatus,	
+    (PRSetsockoptFN)_PR_InvalidStatus,	
+};
+
+static PRIOMethods _pr_tcp_methods = {
+    PR_DESC_SOCKET_TCP,
+    ct_Close,
+    ct_Read,
+    ct_Write,
+    ct_Available,
+    ct_Available64,
+    ct_Synch,
+	(PRSeekFN)_PR_InvalidInt,
+	(PRSeek64FN)_PR_InvalidInt64,
+	(PRFileInfoFN)_PR_InvalidStatus,
+	(PRFileInfo64FN)_PR_InvalidStatus,
+    ct_Writev,
+    ct_Connect,
+    ct_Accept,
+    ct_Bind,
+    ct_Listen,
+    ct_Shutdown,
+    ct_Recv,
+    ct_Send,
+	(PRRecvfromFN)_PR_InvalidInt,
+	(PRSendtoFN)_PR_InvalidInt,
+	(PRPollFN)0,
+    ct_AcceptRead,
+    ct_TransmitFile,
+    ct_GetSockName,
+    ct_GetPeerName,
+    ct_GetSockOpt,
+    ct_SetSockOpt
+};
+
+static PRIOMethods _pr_udp_methods = {
+    PR_DESC_SOCKET_UDP,
+    ct_Close,
+    ct_Read,
+    ct_Write,
+    ct_Available,
+    ct_Available64,
+    ct_Synch,
+	(PRSeekFN)_PR_InvalidInt,
+	(PRSeek64FN)_PR_InvalidInt64,
+	(PRFileInfoFN)_PR_InvalidStatus,
+	(PRFileInfo64FN)_PR_InvalidStatus,
+    ct_Writev,
+    ct_Connect,
+	(PRAcceptFN)_PR_InvalidDesc,
+    ct_Bind,
+    ct_Listen,
+    ct_Shutdown,
+    ct_Recv,
+    ct_Send,
+    ct_RecvFrom,
+    ct_SendTo,
+	(PRPollFN)0,
+	(PRAcceptreadFN)_PR_InvalidInt,
+	(PRTransmitfileFN)_PR_InvalidInt,
+    ct_GetSockName,
+    ct_GetPeerName,
+    ct_GetSockOpt,
+    ct_SetSockOpt
+};
+
+#if defined(_PR_FCNTL_FLAGS)
+#undef _PR_FCNTL_FLAGS
+#endif
+
+#if defined(RHAPSODY)
+#define _PR_FCNTL_FLAGS O_NONBLOCK
+#else
+#error "Can't determine architecture"
+#endif
+    
+static PRFileDesc *ct_SetMethods(PRIntn osfd, PRDescType type)
+{
+    PRInt32 flags, one = 1;
+    PRFileDesc *fd = PR_NEWZAP(PRFileDesc);
+    
+    if (fd == NULL) PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
+    else
+    {
+        fd->secret = PR_NEWZAP(PRFilePrivate);
+        if (NULL == fd->secret)
+        {
+            PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
+            PR_DELETE(fd); fd = NULL;
+        }
+        else
+        {
+            fd->secret->md.osfd = osfd;
+            fd->secret->state = _PR_FILEDESC_OPEN;
+            switch (type)
+            {
+                case PR_DESC_FILE:
+                    fd->methods = PR_GetFileMethods();
+                    break;
+                case PR_DESC_SOCKET_TCP:
+                    fd->methods = PR_GetTCPMethods();
+                    flags = fcntl(osfd, F_GETFL, 0);
+                    flags |= _PR_FCNTL_FLAGS;
+                    (void)fcntl(osfd, F_SETFL, flags);
+                    (void)setsockopt(osfd, SOL_SOCKET, SO_KEEPALIVE,
+                        (_PRSockOptVal_t) &one, sizeof(one));
+                    break;
+                case PR_DESC_SOCKET_UDP:
+                    fd->methods = PR_GetUDPMethods();
+                    flags = fcntl(osfd, F_GETFL, 0);
+                    flags |= _PR_FCNTL_FLAGS;
+                    (void)fcntl(osfd, F_SETFL, flags);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+    return fd;
+}  /* ct_SetMethods */
+
+PR_IMPLEMENT(PRIOMethods*) PR_GetFileMethods()
+{
+    return &_pr_file_methods;
+}  /* PR_GetFileMethods */
+
+PR_IMPLEMENT(PRIOMethods*) PR_GetTCPMethods()
+{
+    return &_pr_tcp_methods;
+}  /* PR_GetTCPMethods */
+
+PR_IMPLEMENT(PRIOMethods*) PR_GetUDPMethods()
+{
+    return &_pr_udp_methods;
+}  /* PR_GetUDPMethods */
+
+PR_IMPLEMENT(PRFileDesc*) PR_AllocFileDesc(PRInt32 osfd, PRIOMethods *methods)
+{
+    PRFileDesc *fd = PR_NEWZAP(PRFileDesc);
+
+    /*
+     * Assert that the file descriptor is small enough to fit in the
+     * fd_set passed to select
+     */
+    PR_ASSERT(osfd < FD_SETSIZE);
+    if (NULL == fd) goto failed;
+
+    fd->secret = PR_NEWZAP(PRFilePrivate);
+    if (NULL == fd->secret) goto failed;
+
+    fd->methods = methods;
+    fd->secret->md.osfd = osfd;
+    /* Make fd non-blocking */
+    if (osfd > 2)
+    {
+        /* Don't mess around with stdin, stdout or stderr */
+        PRIntn flags;
+        flags = fcntl(osfd, F_GETFL, 0);
+        fcntl(osfd, F_SETFL, flags | _PR_FCNTL_FLAGS);
+    }
+    fd->secret->state = _PR_FILEDESC_OPEN;
+    return fd;
+    
+failed:
+    if (NULL != fd)
+    {
+        if (NULL != fd->secret)
+            PR_DELETE(fd->secret);
+        PR_DELETE(fd);
+    }
+    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
+    return fd;
+}  /* PR_AllocFileDesc */
+
+PR_IMPLEMENT(PRFileDesc*) PR_Socket(PRInt32 domain, PRInt32 type, PRInt32 proto)
+{
+    PRIntn osfd;
+    PRDescType ftype;
+    PRFileDesc *fd = NULL;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    if (ct_TestAbort()) return NULL;
+
+    if (PF_INET != domain
+#if defined(_PR_INET6)
+	&& PF_INET6 != domain
+#endif
+	)
+    {
+        PR_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR, 0);
+        return fd;
+    }
+    if (type == SOCK_STREAM) ftype = PR_DESC_SOCKET_TCP;
+    else if (type == SOCK_DGRAM) ftype = PR_DESC_SOCKET_UDP;
+    else
+    {
+        (void)PR_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR, 0);
+        return fd;
+    }
+
+    osfd = socket(domain, type, proto);
+    if (osfd == -1) ct_MapError(_PR_MD_MAP_SOCKET_ERROR, errno);
+    else
+    {
+        fd = ct_SetMethods(osfd, ftype);
+        if (fd == NULL) close(osfd);
+    }
+    return fd;
+}  /* PR_Socket */
+
+/*****************************************************************************/
+/****************************** I/O public methods ***************************/
+/*****************************************************************************/
+
+PR_IMPLEMENT(PRFileDesc*) PR_Open(const char *name, PRIntn flags, PRIntn mode)
+{
+    PRFileDesc *fd = NULL;
+    PRIntn syserrno, osfd = -1, osflags = 0;;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    if (ct_TestAbort()) return NULL;
+
+    if (flags & PR_RDONLY) osflags |= O_RDONLY;
+    if (flags & PR_WRONLY) osflags |= O_WRONLY;
+    if (flags & PR_RDWR) osflags |= O_RDWR;
+    if (flags & PR_APPEND) osflags |= O_APPEND;
+    if (flags & PR_TRUNCATE) osflags |= O_TRUNC;
+
+    /*
+    ** We have to hold the lock across the creation in order to
+    ** enforce the sematics of PR_Rename(). (see the latter for
+    ** more details)
+    */
+    if (flags & PR_CREATE_FILE)
+    {
+        osflags |= O_CREAT;
+        if (NULL !=_pr_rename_lock)
+            PR_Lock(_pr_rename_lock);
+    }
+
+    osfd = open(name, osflags, mode);
+    syserrno = errno;
+
+    if ((flags & PR_CREATE_FILE) && (NULL !=_pr_rename_lock))
+        PR_Unlock(_pr_rename_lock);
+
+    if (osfd == -1)
+        ct_MapError(_PR_MD_MAP_OPEN_ERROR, syserrno);
+    else
+    {
+        fd = ct_SetMethods(osfd, PR_DESC_FILE);
+        if (fd == NULL) close(osfd);  /* $$$ whoops! this is bad $$$ */
+    }
+    return fd;
+}  /* PR_Open */
+
+PR_IMPLEMENT(PRStatus) PR_Delete(const char *name)
+{
+    PRIntn rv = -1;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    rv = unlink(name);
+
+    if (rv == -1) {
+        ct_MapError(_PR_MD_MAP_UNLINK_ERROR, errno);
+        return PR_FAILURE;
+    } else
+        return PR_SUCCESS;
+}  /* PR_Delete */
+
+PR_IMPLEMENT(PRStatus) PR_Access(const char *name, PRAccessHow how)
+{
+    PRIntn rv;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    switch (how)
+    {
+    case PR_ACCESS_READ_OK:
+        rv =  access(name, R_OK);
+        break;
+    case PR_ACCESS_WRITE_OK:
+        rv = access(name, W_OK);
+        break;
+    case PR_ACCESS_EXISTS:
+    default:
+        rv = access(name, F_OK);
+    }
+    if (0 == rv) return PR_SUCCESS;
+    ct_MapError(_PR_MD_MAP_ACCESS_ERROR, errno);
+    return PR_FAILURE;
+    
+}  /* PR_Access */
+
+PR_IMPLEMENT(PRStatus) PR_GetFileInfo(const char *fn, PRFileInfo *info)
+{
+    PRInt32 rv;
+    struct stat sb;
+    PRInt64 s, s2us;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    if ((rv = stat(fn, &sb)) == 0 )
+    {
+        if (info)
+        {
+            if (S_IFREG & sb.st_mode)
+                info->type = PR_FILE_FILE ;
+            else if (S_IFDIR & sb.st_mode)
+                info->type = PR_FILE_DIRECTORY;
+            else
+                info->type = PR_FILE_OTHER;
+            info->size = sb.st_size;
+#if defined(IRIX) && defined(HAVE_LONG_LONG)
+            info->modifyTime = (PR_USEC_PER_SEC * (PRInt64)sb.st_mtim.tv_sec);
+            info->creationTime = (PR_USEC_PER_SEC * (PRInt64)sb.st_ctim.tv_sec);
+#else
+            LL_I2L(s, sb.st_mtime);
+            LL_I2L(s2us, PR_USEC_PER_SEC);
+            LL_MUL(s, s, s2us);
+            info->modifyTime = s;
+            LL_I2L(s, sb.st_ctime);
+            LL_MUL(s, s, s2us);
+            info->creationTime = s;
+#endif
+        }
+    }
+    return (0 == rv) ? PR_SUCCESS : PR_FAILURE;
+}  /* PR_GetFileInfo */
+
+PR_IMPLEMENT(PRStatus) PR_Rename(const char *from, const char *to)
+{
+    PRIntn rv = -1;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    /*
+    ** We have to acquire a lock here to stiffle anybody trying to create
+    ** a new file at the same time. And we have to hold that lock while we
+    ** test to see if the file exists and do the rename. The other place
+    ** where the lock is held is in PR_Open() when possibly creating a 
+    ** new file.
+    */
+
+    PR_Lock(_pr_rename_lock);
+    rv = access(to, F_OK);
+    if (0 == rv)
+    {
+        PR_SetError(PR_FILE_EXISTS_ERROR, 0);
+        rv = -1;
+    }
+    else
+    {
+        rv = rename(from, to);
+        if (rv == -1)
+            ct_MapError(_PR_MD_MAP_RENAME_ERROR, errno);
+    }
+    PR_Unlock(_pr_rename_lock);
+    return (-1 == rv) ? PR_FAILURE : PR_SUCCESS;
+}  /* PR_Rename */
+
+PR_IMPLEMENT(PRStatus) PR_CloseDir(PRDir *dir)
+{
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    if (NULL != dir->md.d)
+    {
+        closedir(dir->md.d);
+        dir->md.d = NULL;
+    }
+    return PR_SUCCESS;
+}  /* PR_CloseDir */
+
+PR_IMPLEMENT(PRStatus) PR_MkDir(const char *name, PRIntn mode)
+{
+    PRInt32 rv = -1;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    /*
+    ** This lock is used to enforce rename semantics as described
+    ** in PR_Rename.
+    */
+    if (NULL !=_pr_rename_lock)
+        PR_Lock(_pr_rename_lock);
+    rv = mkdir(name, mode);
+    if (-1 == rv)
+        ct_MapError(_PR_MD_MAP_MKDIR_ERROR, errno);
+    if (NULL !=_pr_rename_lock)
+        PR_Unlock(_pr_rename_lock);
+
+    return (-1 == rv) ? PR_FAILURE : PR_SUCCESS;
+}  /* PR_Mkdir */
+
+PR_IMPLEMENT(PRStatus) PR_RmDir(const char *name)
+{
+    PRInt32 rv;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    rv = rmdir(name);
+    if (0 == rv) {
+    return PR_SUCCESS;
+    } else {
+    ct_MapError(_PR_MD_MAP_RMDIR_ERROR, errno);
+    return PR_FAILURE;
+    }
+}  /* PR_Rmdir */
+
+
+PR_IMPLEMENT(PRDir*) PR_OpenDir(const char *name)
+{
+    DIR *osdir;
+    PRDir *dir = NULL;
+
+    if (ct_TestAbort()) return dir;
+
+    osdir = opendir(name);
+    if (osdir == NULL)
+        ct_MapError(_PR_MD_MAP_OPENDIR_ERROR, errno);
+    else
+    {
+        dir = PR_NEWZAP(PRDir);
+        dir->md.d = osdir;
+    }
+    return dir;
+}  /* PR_OpenDir */
+
+PR_IMPLEMENT(PRInt32) PR_Poll(
+    PRPollDesc *pds, PRIntn npds, PRIntervalTime timeout)
+{
+    PRInt32 ready = 0;
+    /*
+     * For restarting poll() if it is interrupted by a signal.
+     * We use these variables to figure out how much time has
+     * elapsed and how much of the timeout still remains.
+     */
+    PRIntervalTime start, elapsed, remaining;
+
+    if (0 == npds) PR_Sleep(timeout);
+    else
+    {
+        PRIntn index, msecs;
+        struct pollfd *syspoll = NULL;
+        syspoll = (struct pollfd*)PR_MALLOC(npds * sizeof(struct pollfd));
+        for (index = 0; index < npds; ++index)
+        {
+            PRFileDesc *bottom = pds[index].fd;
+
+            if (bottom == NULL)
+            {
+                /* make poll() ignore this entry */
+                syspoll[index].fd = -1;
+                continue;
+            }
+
+            while (bottom->lower != NULL) bottom = bottom->lower;
+            syspoll[index].fd = bottom->secret->md.osfd;
+
+            syspoll[index].events = 0;
+            if (pds[index].in_flags & PR_POLL_READ)
+                syspoll[index].events |= POLLIN;
+            if (pds[index].in_flags & PR_POLL_WRITE)
+                syspoll[index].events |= POLLOUT;
+            if (pds[index].in_flags & PR_POLL_EXCEPT)
+                syspoll[index].events |= POLLPRI;
+            if (_PR_FILEDESC_OPEN == bottom->secret->state)
+                pds[index].out_flags = 0;  /* init the result */
+            else
+            {
+                ready += 1;  /* this will cause an abrupt return */
+                pds[index].out_flags = POLLNVAL;  /* bogii */
+            }
+        }
+        if (0 == ready)
+        {
+            switch (timeout)
+            {
+            case PR_INTERVAL_NO_WAIT: msecs = 0; break;
+            case PR_INTERVAL_NO_TIMEOUT: msecs = -1; break;
+            default:
+                msecs = PR_IntervalToMilliseconds(timeout);
+                start = PR_IntervalNow();
+            }
+
+retry:
+            ready = poll(syspoll, npds, msecs);
+            if (-1 == ready)
+            {
+                PRIntn oserror = errno;
+                PRErrorCode prerror;
+
+                switch (oserror) {
+                    case EAGAIN:
+                        prerror = PR_INSUFFICIENT_RESOURCES_ERROR;
+                        break;
+                    case EINTR:
+                        if (timeout == PR_INTERVAL_NO_TIMEOUT)
+                            goto retry;
+                        else if (timeout == PR_INTERVAL_NO_WAIT)
+                            ready = 0;  /* don't retry, just time out */
+                        {
+                            elapsed = (PRIntervalTime) (PR_IntervalNow()
+                                    - start);
+                            if (elapsed > timeout)
+                                ready = 0;  /* timed out */
+                            else
+                            {
+                                remaining = timeout - elapsed;
+                                msecs = PR_IntervalToMilliseconds(remaining);
+                                goto retry;
+                            }
+                        }
+                        break;
+                case EINVAL:
+                        prerror = PR_INVALID_ARGUMENT_ERROR;
+                        break;
+                    case EFAULT:
+                        prerror = PR_ACCESS_FAULT_ERROR;
+                        break;
+                    default:
+                        prerror = PR_UNKNOWN_ERROR;
+                        break;
+                } 
+                PR_SetError(prerror, oserror);
+            }
+            else if (ready > 0)
+            {
+                for (index = 0; index < npds; ++index)
+                {
+                    if (pds[index].fd == NULL) continue;
+                    PR_ASSERT(0 == pds[index].out_flags);
+                    if (0 != syspoll[index].revents)
+                    {
+                        if (syspoll[index].revents & POLLIN)
+                            pds[index].out_flags |= PR_POLL_READ;
+                        if (syspoll[index].revents & POLLOUT)
+                            pds[index].out_flags |= PR_POLL_WRITE;
+                        if (syspoll[index].revents & POLLPRI)
+                            pds[index].out_flags |= PR_POLL_EXCEPT;
+                        if (syspoll[index].revents & POLLERR)
+                            pds[index].out_flags |= PR_POLL_ERR;
+                        if (syspoll[index].revents & POLLNVAL)
+                            pds[index].out_flags |= PR_POLL_NVAL;
+                    }
+                }
+            }
+        }
+
+        PR_DELETE(syspoll);
+
+    }
+    return ready;
+
+} /* PR_Poll */
+
+PR_IMPLEMENT(PRDirEntry*) PR_ReadDir(PRDir *dir, PRDirFlags flags)
+{
+    struct dirent *dp;
+
+    if (ct_TestAbort()) return NULL;
+
+    for (;;)
+    {
+        dp = readdir(dir->md.d);
+        if (NULL == dp) return NULL;
+        if ((flags & PR_SKIP_DOT)
+            && ('.' == dp->d_name[0])
+            && (0 == dp->d_name[1])) continue;
+        if ((flags & PR_SKIP_DOT_DOT)
+            && ('.' == dp->d_name[0])
+            && ('.' == dp->d_name[1])
+            && (0 == dp->d_name[2])) continue;
+        break;
+    }
+    dir->d.name = dp->d_name;
+    return &dir->d;
+}  /* PR_ReadDir */
+
+PR_IMPLEMENT(PRFileDesc*) PR_NewUDPSocket()
+{
+    PRFileDesc *fd = NULL;
+    PRIntn osfd = -1, syserrno;
+    PRIntn domain = PF_INET;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    if (ct_TestAbort()) return NULL;
+
+#if defined(_PR_INET6)
+    if (_pr_ipv6_enabled)
+        domain = PF_INET6;
+#endif
+    osfd = socket(domain, SOCK_DGRAM, 0);
+    syserrno = errno;
+
+    if (osfd == -1)
+        ct_MapError(_PR_MD_MAP_SOCKET_ERROR, syserrno);
+    else
+    {
+        fd = ct_SetMethods(osfd, PR_DESC_SOCKET_UDP);
+        if (fd == NULL) close(osfd);
+    }
+    return fd;
+}  /* PR_NewUDPSocket */
+
+PR_IMPLEMENT(PRFileDesc*) PR_NewTCPSocket()
+{
+    PRIntn osfd = -1;
+    PRFileDesc *fd = NULL;
+    PRIntn domain = PF_INET;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    if (ct_TestAbort()) return NULL;
+
+#if defined(_PR_INET6)
+    if (_pr_ipv6_enabled)
+        domain = PF_INET6;
+#endif
+    osfd = socket(domain, SOCK_STREAM, 0);
+
+    if (osfd == -1)
+        ct_MapError(_PR_MD_MAP_SOCKET_ERROR, errno);
+    else
+    {
+        fd = ct_SetMethods(osfd, PR_DESC_SOCKET_TCP);
+        if (fd == NULL) close(osfd);
+    }
+    return fd;
+}  /* PR_NewTCPSocket */
+
+PR_IMPLEMENT(PRStatus) PR_NewTCPSocketPair(PRFileDesc *fds[2])
+{
+    PRInt32 osfd[2];
+    PRIntn flags;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    if (socketpair(AF_UNIX, SOCK_STREAM, 0, osfd) == -1) {
+    ct_MapError(_PR_MD_MAP_SOCKETPAIR_ERROR, errno);
+    return PR_FAILURE;
+    }
+
+    fds[0] = ct_SetMethods(osfd[0], PR_DESC_SOCKET_TCP);
+    if (fds[0] == NULL) {
+        close(osfd[0]);
+        close(osfd[1]);
+        return PR_FAILURE;
+    }
+    fds[1] = ct_SetMethods(osfd[1], PR_DESC_SOCKET_TCP);
+    if (fds[1] == NULL) {
+        PR_Close(fds[0]);
+        close(osfd[1]);
+        return PR_FAILURE;
+    }
+    return PR_SUCCESS;
+}  /* PR_NewTCPSocketPair */
+
+PR_IMPLEMENT(PRStatus) PR_CreatePipe(
+    PRFileDesc **readPipe,
+    PRFileDesc **writePipe
+)
+{
+    int pipefd[2];
+    int flags;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    if (pipe(pipefd) == -1)
+    {
+	/* XXX map pipe error */
+        PR_SetError(PR_UNKNOWN_ERROR, errno);
+        return PR_FAILURE;
+    }
+    *readPipe = ct_SetMethods(pipefd[0], PR_DESC_FILE);
+    if (NULL == *readPipe)
+    {
+        close(pipefd[0]);
+        close(pipefd[1]);
+        return PR_FAILURE;
+    }
+    flags = fcntl(pipefd[0], F_GETFL, 0);
+    flags |= _PR_FCNTL_FLAGS;
+    (void)fcntl(pipefd[0], F_SETFL, flags);
+    *writePipe = ct_SetMethods(pipefd[1], PR_DESC_FILE);
+    if (NULL == *writePipe)
+    {
+        PR_Close(*readPipe);
+        close(pipefd[1]);
+        return PR_FAILURE;
+    }
+    flags = fcntl(pipefd[1], F_GETFL, 0);
+    flags |= _PR_FCNTL_FLAGS;
+    (void)fcntl(pipefd[1], F_SETFL, flags);
+    return PR_SUCCESS;
+}
+
+/*****************************************************************************/
+/***************************** I/O friends methods ***************************/
+/*****************************************************************************/
+
+PR_IMPLEMENT(PRFileDesc*) PR_ImportFile(PRInt32 osfd)
+{
+    PRFileDesc *fd = ct_SetMethods(osfd, PR_DESC_FILE);
+    if (NULL != fd) fd->secret->state = _PR_FILEDESC_OPEN;
+    else close(osfd);
+    return fd;
+}  /* PR_ImportFile */
+
+PR_IMPLEMENT(PRFileDesc*) PR_ImportTCPSocket(PRInt32 osfd)
+{
+    PRFileDesc *fd = ct_SetMethods(osfd, PR_DESC_SOCKET_TCP);
+    if (NULL != fd) fd->secret->state = _PR_FILEDESC_OPEN;
+    else close(osfd);
+    return fd;
+}  /* PR_ImportTCPSocket */
+
+PR_IMPLEMENT(PRFileDesc*) PR_ImportUDPSocket(PRInt32 osfd)
+{
+    PRFileDesc *fd = ct_SetMethods(osfd, PR_DESC_SOCKET_UDP);
+    if (NULL != fd) fd->secret->state = _PR_FILEDESC_OPEN;
+    else close(osfd);
+    return fd;
+}  /* PR_ImportUDPSocket */
+
+PR_IMPLEMENT(PRInt32) PR_FileDesc2NativeHandle(PRFileDesc *fd)
+{
+    if (fd)
+    {
+        /*
+         * The fd may be layered.  Chase the links to the
+         * bottom layer to get the osfd.
+         */
+        PRFileDesc *bottom = fd;
+        while (bottom->lower != NULL) {
+            bottom = bottom->lower;
+        }
+        return bottom->secret->md.osfd;
+    }
+    else
+    {
+        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
+        return -1;
+    }
+}  /* PR_FileDesc2NativeHandle */
+
+PR_IMPLEMENT(void) PR_ChangeFileDescNativeHandle(PRFileDesc *fd,
+    PRInt32 handle)
+{
+    if (fd) fd->secret->md.osfd = handle;
+}  /*  PR_ChangeFileDescNativeHandle*/
+
+PR_IMPLEMENT(PRStatus) PR_LockFile(PRFileDesc *fd)
+{
+    PRStatus status = PR_SUCCESS;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    PR_Lock(_pr_flock_lock);
+    if (0 == fd->secret->lockCount)
+    {
+        status = _PR_MD_LOCKFILE(fd->secret->md.osfd);
+        if (PR_SUCCESS == status) fd->secret->lockCount = 1;
+    }
+    else fd->secret->lockCount += 1;
+    PR_Unlock(_pr_flock_lock);
+ 
+    return status;
+}  /* PR_LockFile */
+
+PR_IMPLEMENT(PRStatus) PR_TLockFile(PRFileDesc *fd)
+{
+    PRStatus status = PR_SUCCESS;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    PR_Lock(_pr_flock_lock);
+    if (0 == fd->secret->lockCount)
+    {
+        status = _PR_MD_TLOCKFILE(fd->secret->md.osfd);
+        if (PR_SUCCESS == status) fd->secret->lockCount = 1;
+    }
+    else fd->secret->lockCount += 1;
+    PR_Unlock(_pr_flock_lock);
+ 
+    return status;
+}  /* PR_TLockFile */
+
+PR_IMPLEMENT(PRStatus) PR_UnlockFile(PRFileDesc *fd)
+{
+    PRStatus status = PR_SUCCESS;
+
+    if (ct_TestAbort()) return PR_FAILURE;
+
+    PR_Lock(_pr_flock_lock);
+    if (fd->secret->lockCount == 1)
+    {
+        status = _PR_MD_UNLOCKFILE(fd->secret->md.osfd);
+        if (PR_SUCCESS == status) fd->secret->lockCount = 0;
+    }
+    else fd->secret->lockCount -= 1;
+    PR_Unlock(_pr_flock_lock);
+
+    return status;
+}
+
+/*
+ * $$$
+ * I wonder whether we should have the following two functions in the API.
+ * However, it seems like some existing code is using them.  So I have to
+ * have them here.  --WTC
+ * $$$
+ */
+
+PRInt32 PR_GetSysfdTableMax(void)
+{
+#if defined(XP_UNIX) && !defined(AIX)
+    struct rlimit rlim;
+
+    if ( getrlimit(RLIMIT_NOFILE, &rlim) < 0) 
+       return -1;
+
+    return rlim.rlim_max;
+#elif defined(AIX)
+    return sysconf(_SC_OPEN_MAX);
+#endif
+}
+
+PRInt32 PR_SetSysfdTableSize(PRIntn table_size)
+{
+#if defined(XP_UNIX) && !defined(AIX)
+    struct rlimit rlim;
+    PRInt32 tableMax = PR_GetSysfdTableMax();
+
+    if (tableMax < 0) return -1;
+    rlim.rlim_max = tableMax;
+
+    /* Grow as much as we can; even if too big */
+    if ( rlim.rlim_max < table_size )
+        rlim.rlim_cur = rlim.rlim_max;
+    else
+        rlim.rlim_cur = table_size;
+
+    if ( setrlimit(RLIMIT_NOFILE, &rlim) < 0) 
+        return -1;
+
+    return rlim.rlim_cur;
+#elif defined(AIX)
+    return -1;
+#endif
+}
+
+/*
+ * PR_Stat is supported for backward compatibility; some existing Java
+ * code uses it.  New code should use PR_GetFileInfo.
+ */
+
+#ifndef NO_NSPR_10_SUPPORT
+PR_IMPLEMENT(PRInt32) PR_Stat(const char *name, struct stat *buf)
+{
+    PRIntn rv;
+
+    if (ct_TestAbort()) return -1;
+
+    rv = stat(name, buf);
+    if (-1 == rv) {
+        ct_MapError(_PR_MD_MAP_STAT_ERROR, errno);
+        return PR_FAILURE;
+    } else {
+        return PR_SUCCESS;
+    }
+}
+#endif /* ! NO_NSPR_10_SUPPORT */
+
+/*
+ * $$$
+ * The following is the old PR_Select() compatibility code.  Existing
+ * code uses it, so I have to copy this code from prsocket.c to this
+ * file.  Alan will be upset to find out that I did this while he is
+ * on vacation touring the distilleries in Scotland.  --WTC
+ *
+ * Luckily touring distilleries is like taking valium.  --AOF
+
+ * $$$
+ */
+
+PR_IMPLEMENT(void) PR_FD_ZERO(PR_fd_set *set)
+{
+    memset(set, 0, sizeof(PR_fd_set));
+}
+
+PR_IMPLEMENT(void) PR_FD_SET(PRFileDesc *fh, PR_fd_set *set)
+{
+    PR_ASSERT( set->hsize < PR_MAX_SELECT_DESC );
+
+    set->harray[set->hsize++] = fh;
+}
+
+PR_IMPLEMENT(void) PR_FD_CLR(PRFileDesc *fh, PR_fd_set *set)
+{
+    PRUint32 index, index2;
+
+    for (index = 0; index<set->hsize; index++)
+       if (set->harray[index] == fh) {
+           for (index2=index; index2 < (set->hsize-1); index2++) {
+               set->harray[index2] = set->harray[index2+1];
+           }
+           set->hsize--;
+           break;
+       }
+}
+
+PR_IMPLEMENT(PRInt32) PR_FD_ISSET(PRFileDesc *fh, PR_fd_set *set)
+{
+    PRUint32 index;
+    for (index = 0; index<set->hsize; index++)
+       if (set->harray[index] == fh) {
+           return 1;
+       }
+    return 0;
+}
+
+PR_IMPLEMENT(void) PR_FD_NSET(PRInt32 fd, PR_fd_set *set)
+{
+    PR_ASSERT( set->nsize < PR_MAX_SELECT_DESC );
+
+    set->narray[set->nsize++] = fd;
+}
+
+PR_IMPLEMENT(void) PR_FD_NCLR(PRInt32 fd, PR_fd_set *set)
+{
+    PRUint32 index, index2;
+
+    for (index = 0; index<set->nsize; index++)
+       if (set->narray[index] == fd) {
+           for (index2=index; index2 < (set->nsize-1); index2++) {
+               set->narray[index2] = set->narray[index2+1];
+           }
+           set->nsize--;
+           break;
+       }
+}
+
+PR_IMPLEMENT(PRInt32) PR_FD_NISSET(PRInt32 fd, PR_fd_set *set)
+{
+    PRUint32 index;
+    for (index = 0; index<set->nsize; index++)
+       if (set->narray[index] == fd) {
+           return 1;
+       }
+    return 0;
+}
+
+#include <sys/types.h>
+#include <sys/time.h>
+#if !defined(SUNOS4) && !defined(HPUX) && !defined(LINUX)
+#include <sys/select.h>
+#endif
+
+static PRInt32
+_PR_getset(PR_fd_set *pr_set, fd_set *set)
+{
+    PRUint32 index;
+    PRInt32 max = 0;
+
+    if (!pr_set)
+        return 0;
+   
+    FD_ZERO(set);
+
+    /* First set the pr file handle osfds */
+    for (index=0; index<pr_set->hsize; index++) {
+        FD_SET(pr_set->harray[index]->secret->md.osfd, set);
+        if (pr_set->harray[index]->secret->md.osfd > max)
+            max = pr_set->harray[index]->secret->md.osfd;
+    }
+    /* Second set the native osfds */
+    for (index=0; index<pr_set->nsize; index++) {
+        FD_SET(pr_set->narray[index], set);
+        if (pr_set->narray[index] > max)
+            max = pr_set->narray[index];
+    }
+    return max;
+}
+
+static void
+_PR_setset(PR_fd_set *pr_set, fd_set *set)
+{
+    PRUint32 index, last_used;
+
+    if (!pr_set)
+        return;
+
+    for (last_used=0, index=0; index<pr_set->hsize; index++) {
+        if ( FD_ISSET(pr_set->harray[index]->secret->md.osfd, set) ) {
+            pr_set->harray[last_used++] = pr_set->harray[index];
+        }
+    }
+    pr_set->hsize = last_used;
+
+    for (last_used=0, index=0; index<pr_set->nsize; index++) {
+        if ( FD_ISSET(pr_set->narray[index], set) ) {
+            pr_set->narray[last_used++] = pr_set->narray[index];
+        }
+    }
+    pr_set->nsize = last_used;
+}
+
+PR_IMPLEMENT(PRInt32) PR_Select(PRInt32 unused, PR_fd_set *pr_rd, PR_fd_set *pr_wr, 
+                             PR_fd_set *pr_ex, PRIntervalTime timeout)
+{
+    fd_set rd, wr, ex;
+    struct timeval tv, *tvp;
+    PRInt32 max, max_fd;
+    PRInt32 rv;
+    /*
+     * For restarting select() if it is interrupted by a Unix signal.
+     * We use these variables to figure out how much time has elapsed
+     * and how much of the timeout still remains.
+     */
+    PRIntervalTime start, elapsed, remaining;
+
+    FD_ZERO(&rd);
+    FD_ZERO(&wr);
+    FD_ZERO(&ex);
+
+    max_fd = _PR_getset(pr_rd, &rd);
+    max_fd = (max = _PR_getset(pr_wr, &wr))>max_fd?max:max_fd;
+    max_fd = (max = _PR_getset(pr_ex, &ex))>max_fd?max:max_fd;
+
+    if (timeout == PR_INTERVAL_NO_TIMEOUT) {
+        tvp = NULL;
+    } else {
+        tv.tv_sec = PR_IntervalToSeconds(timeout);
+        tv.tv_usec = PR_IntervalToMicroseconds(
+                timeout - PR_SecondsToInterval(tv.tv_sec));
+        tvp = &tv;
+        start = PR_IntervalNow();
+    }
+
+retry:
+    rv = select(max_fd + 1, (_PRSelectFdSetArg_t) &rd,
+        (_PRSelectFdSetArg_t) &wr, (_PRSelectFdSetArg_t) &ex, tvp);
+
+    if (rv == -1 && errno == EINTR) {
+        if (timeout == PR_INTERVAL_NO_TIMEOUT) {
+            goto retry;
+        } else {
+            elapsed = (PRIntervalTime) (PR_IntervalNow() - start);
+            if (elapsed > timeout) {
+                rv = 0;  /* timed out */
+            } else {
+                remaining = timeout - elapsed;
+                tv.tv_sec = PR_IntervalToSeconds(remaining);
+                tv.tv_usec = PR_IntervalToMicroseconds(
+                        remaining - PR_SecondsToInterval(tv.tv_sec));
+                goto retry;
+            }
+        }
+    }
+
+    if (rv > 0) {
+        _PR_setset(pr_rd, &rd);
+        _PR_setset(pr_wr, &wr);
+        _PR_setset(pr_ex, &ex);
+    } else if (rv == -1) {
+        ct_MapError(_PR_MD_MAP_SELECT_ERROR, errno);
+    }
+    return rv;
+}
+
+#endif /* defined(_PR_CTHREADS) */
+
+/* ptio.c */
new file mode 100644
--- /dev/null
+++ b/pr/src/cthreads/ctmisc.c
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * The contents of this file are subject to the Netscape Public License
+ * Version 1.0 (the "NPL"); you may not use this file except in
+ * compliance with the NPL.  You may obtain a copy of the NPL at
+ * http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the NPL is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
+ * for the specific language governing rights and limitations under the
+ * NPL.
+ *
+ * The Initial Developer of this code under the NPL is Netscape
+ * Communications Corporation.  Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
+ * Reserved.
+ */
+
+/*
+** File:   ctmisc.c
+** Descritpion:  Implemenation of miscellaneous methods for cthreads
+*/
+
+#if defined(_PR_CTHREADS)
+
+#include "primpl.h"
+
+#include <stdio.h>
+
+#if defined(_PR_NO_CTHREAD_KEY_T)
+
+#define MAX_KEYS 15
+
+static int cur_key;
+
+typedef void (*destructor_t)(any_t);
+
+static destructor_t key_destructors[MAX_KEYS];
+static mutex_t key_mutex = NULL;
+
+static void
+create_key_mutex()
+{
+  key_mutex = mutex_alloc();
+  PR_ASSERT(key_mutex != NULL);
+
+  mutex_init(key_mutex);
+}
+
+int
+cthread_key_create(cthread_key_t *key, destructor_t destructor)
+{
+  if (!key_mutex) create_key_mutex();
+
+  mutex_lock(key_mutex);
+
+  if (cur_key == MAX_KEYS - 1)
+  {
+    mutex_unlock(key_mutex);
+    return 1;
+  }
+
+  *key = cur_key;
+
+  key_destructors[cur_key] = destructor;
+
+  cur_key ++;
+
+  mutex_unlock(key_mutex);
+  return 0;
+}
+
+int
+cthread_setspecific(cthread_key_t key, any_t value)
+{
+  any_t *key_values = cthread_data(cthread_self());
+
+  if (!key_values)
+  {
+    key_values = (any_t*)calloc(MAX_KEYS, sizeof(any_t));
+
+    if (!key_values) return 1;
+
+    cthread_set_data(cthread_self(), key_values);
+  }
+
+  key_values[key] = value;
+  return 0;
+}
+
+int
+cthread_getspecific(cthread_key_t key, any_t value)
+{
+  any_t *key_values = cthread_data(cthread_self());
+
+  if (!key_values)
+  {
+    key_values = (any_t*)calloc(MAX_KEYS, sizeof(any_t));
+
+    if (!key_values) return 1;
+
+    cthread_set_data(cthread_self(), key_values);
+  }
+
+  *((any_t*)value) = key_values[key];
+  return 0;
+}
+
+void
+_cthread_callkeydestructors()
+{
+  any_t *key_values = cthread_data(cthread_self());
+  int i;
+
+  for (i = 0; i < MAX_KEYS; i ++)
+    if (key_destructors[i]) (*key_destructors[i])(key_values[i]);
+
+  free(key_values);
+}
+
+#endif /* _PR_CTHREAD_NO_CTHREAD_KEY_T */
+
+#if 0 && defined(DEBUG)
+#define CT_LOG(f) \
+{ \
+    static PRBool didthis = PR_FALSE; \
+    if (!didthis) \
+    { \
+        printf("Empty function called: %s\n", f); \
+        didthis = PR_TRUE; \
+    } \
+}
+#else
+#define PT_LOG(f)
+#endif
+
+PR_IMPLEMENT(void) _MD_INIT_LOCKS() {PT_LOG("_MD_INIT_LOCKS")}
+PR_IMPLEMENT(void) _PR_MD_START_INTERRUPTS() {PT_LOG("_PR_MD_START_INTERRUPTS")}
+
+PR_IMPLEMENT(void) _PR_InitCPUs(void) {PT_LOG("_PR_InitCPUs")}
+PR_IMPLEMENT(void) _PR_InitStacks(void) {PT_LOG("_PR_InitStacks")}
+PR_IMPLEMENT(void) _PR_InitTPD(void) {PT_LOG("_PR_InitTPD")}
+
+PR_IMPLEMENT(void) PR_SetConcurrency(PRUintn numCPUs)  {PT_LOG("PR_SetConcurrency")}
+
+PR_IMPLEMENT(void) PR_SetThreadRecycleMode(PRUint32 flag)
+    {PT_LOG("PR_SetThreadRecycleMode")}
+
+#endif /* defined(_PR_CTHREADS) */
+
+/* ctmisc.c */
new file mode 100644
--- /dev/null
+++ b/pr/src/cthreads/ctsynch.c
@@ -0,0 +1,701 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * The contents of this file are subject to the Netscape Public License
+ * Version 1.0 (the "NPL"); you may not use this file except in
+ * compliance with the NPL.  You may obtain a copy of the NPL at
+ * http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the NPL is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
+ * for the specific language governing rights and limitations under the
+ * NPL.
+ *
+ * The Initial Developer of this code under the NPL is Netscape
+ * Communications Corporation.  Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
+ * Reserved.
+ */
+
+/*
+** File:            ptsynch.c
+** Descritpion:        Implemenation for thread synchronization using pthreads
+** Exports:            prlock.h, prcvar.h, prmon.h, prcmon.h
+*/
+
+#if defined(_PR_CTHREADS)
+
+#include "primpl.h"
+#include "obsolete/prsem.h"
+
+#include <string.h>
+#include <mach/cthreads.h>
+#include <sys/time.h>
+
+/**************************************************************/
+/**************************************************************/
+/*****************************LOCKS****************************/
+/**************************************************************/
+/**************************************************************/
+
+void _PR_InitLocks(void)
+{
+    _PR_MD_INIT_LOCKS();
+}
+
+static void pt_PostNotifies(PRLock *lock, PRBool unlock)
+{
+    PRIntn index, rv;
+    _CT_Notified post;
+    _CT_Notified *notified, *prev = NULL;
+    /*
+     * Time to actually notify any conditions that were affected
+     * while the lock was held. Get a copy of the list that's in
+     * the lock structure and then zero the original. If it's
+     * linked to other such structures, we own that storage.
+     */
+    post = lock->notified;  /* a safe copy; we own the lock */
+
+#if defined(DEBUG)
+    memset(&lock->notified, 0, sizeof(_CT_Notified));  /* reset */
+#else
+    lock->notified.length = 0;  /* these are really sufficient */
+    lock->notified.link = NULL;
+#endif
+
+    /* should (may) we release lock before notifying? */
+    if (unlock)
+    {
+        rv = mutex_unlock(lock->mutex);
+        PR_ASSERT(0 == rv);
+    }
+
+    notified = &post;  /* this is where we start */
+    do
+    {
+        for (index = 0; index < notified->length; ++index)
+        {
+            PR_ASSERT(NULL != notified->cv[index].cv);
+            PR_ASSERT(0 != notified->cv[index].times);
+            if (-1 == notified->cv[index].times)
+            {
+                condition_broadcast(notified->cv[index].cv->cv);
+            }
+            else
+            {
+                while (notified->cv[index].times-- > 0)
+                {
+                    condition_signal(notified->cv[index].cv->cv);
+                }
+            }
+        }
+        prev = notified;
+        notified = notified->link;
+        if (&post != prev) PR_DELETE(prev);
+    } while (NULL != notified);
+}  /* pt_PostNotifies */
+
+PR_IMPLEMENT(PRLock*) PR_NewLock(void)
+{
+    PRLock *lock;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    lock = PR_NEWZAP(PRLock);
+    if (lock != NULL)
+    {
+	lock->mutex = mutex_alloc();
+	if (lock->mutex == NULL)
+	{
+		PR_DELETE(lock);
+		return NULL;
+	}
+	mutex_init(lock->mutex);
+    }
+    return lock;
+}  /* PR_NewLock */
+
+PR_IMPLEMENT(void) PR_DestroyLock(PRLock *lock)
+{
+    PR_ASSERT(NULL != lock);
+    PR_ASSERT(CTHREAD_THR_HANDLE_IS_ZERO(lock->owner));
+    PR_ASSERT(0 == lock->notified.length);
+    PR_ASSERT(NULL == lock->notified.link);
+    mutex_free(lock->mutex);
+#if defined(DEBUG)
+    memset(lock, 0xaf, sizeof(PRLock));
+#endif
+    PR_DELETE(lock);
+}  /* PR_DestroyLock */
+
+PR_IMPLEMENT(void) PR_Lock(PRLock *lock)
+{
+    PR_ASSERT(lock != NULL);
+    mutex_lock(lock->mutex);
+    PR_ASSERT(0 == lock->notified.length);
+    PR_ASSERT(NULL == lock->notified.link);
+    PR_ASSERT(CTHREAD_THR_HANDLE_IS_ZERO(lock->owner));
+    lock->owner = cthread_self();
+}  /* PR_Lock */
+
+PR_IMPLEMENT(PRStatus) PR_Unlock(PRLock *lock)
+{
+    PR_ASSERT(lock != NULL);
+    PR_ASSERT(CTHREAD_MUTEX_IS_LOCKED(lock->mutex));
+    PR_ASSERT(lock->owner == cthread_self());
+
+    if (!lock->owner == cthread_self())
+        return PR_FAILURE;
+
+    CTHREAD_ZERO_THR_HANDLE(lock->owner);
+    if (0 == lock->notified.length)  /* shortcut */
+    {
+        mutex_unlock(lock->mutex);
+    }
+    else pt_PostNotifies(lock, PR_TRUE);
+
+    return PR_SUCCESS;
+}  /* PR_Unlock */
+
+
+/**************************************************************/
+/**************************************************************/
+/***************************CONDITIONS*************************/
+/**************************************************************/
+/**************************************************************/
+
+#if 0
+/*
+ * This code is used to compute the absolute time for the wakeup.
+ * It's moderately ugly, so it's defined here and called in a
+ * couple of places.
+ */
+#define CT_NANOPERMICRO 1000UL
+#define CT_BILLION 1000000000UL
+
+static PRStatus ct_TimedWait(
+    condition_t cv, mutex_t ml, PRIntervalTime timeout)
+{
+    int rv;
+    struct timeval now;
+    struct timespec tmo;
+    PRUint32 ticks = PR_TicksPerSecond();
+
+    tmo.tv_sec = timeout / ticks;
+    tmo.tv_nsec = timeout - (tmo.tv_sec * ticks);
+    tmo.tv_nsec = PR_IntervalToMicroseconds(CT_NANOPERMICRO * tmo.tv_nsec);
+
+    /* pthreads wants this in absolute time, off we go ... */
+#if defined(SOLARIS) && defined(_SVID_GETTOD)
+    (void)gettimeofday(&now);
+#else
+    (void)gettimeofday(&now, NULL);
+#endif
+    /* that one's usecs, this one's nsecs - grrrr! */
+    tmo.tv_sec += now.tv_sec;
+    tmo.tv_nsec += (CT_NANOPERMICRO * now.tv_usec);
+    tmo.tv_sec += tmo.tv_nsec / CT_BILLION;
+    tmo.tv_nsec %= CT_BILLION;
+
+    rv = pthread_cond_timedwait(cv, ml, &tmo);
+
+    /* NSPR doesn't report timeouts */
+#ifdef _PR_DCETHREADS
+    return (rv == -1 && errno == EAGAIN) ? 0 : rv;
+#else
+    return (rv == ETIMEDOUT) ? 0 : rv;
+#endif
+}  /* ct_TimedWait */
+#endif
+
+
+/*
+ * Notifies just get posted to the to the protecting mutex. The
+ * actual notification is done when the lock is released so that
+ * MP systems don't contend for a lock that they can't have.
+ */
+static void ct_PostNotifyToCvar(PRCondVar *cvar, PRBool broadcast)
+{
+    PRIntn index = 0;
+    _CT_Notified *notified = &cvar->lock->notified;
+
+    PR_ASSERT(cvar->lock->owner == cthread_self());
+    PR_ASSERT(CTHREAD_MUTEX_IS_LOCKED(cvar->lock->mutex));
+
+    while (1)
+    {
+        for (index = 0; index < notified->length; ++index)
+        {
+            if (notified->cv[index].cv == cvar)
+            {
+                if (broadcast)
+                    notified->cv[index].times = -1;
+                else if (-1 != notified->cv[index].times)
+                    notified->cv[index].times += 1;
+                goto finished;  /* we're finished */
+            }
+        }
+        /* if not full, enter new CV in this array */
+        if (notified->length < CT_CV_NOTIFIED_LENGTH) break;
+
+        /* if there's no link, create an empty array and link it */
+        if (NULL == notified->link)
+            notified->link = PR_NEWZAP(_CT_Notified);
+        notified = notified->link;
+    }
+
+    /* A brand new entry in the array */
+    notified->cv[index].times = (broadcast) ? -1 : 1;
+    notified->cv[index].cv = cvar;
+    notified->length += 1;
+
+finished:
+    PR_ASSERT(cvar->lock->owner == cthread_self());
+}  /* ct_PostNotifyToCvar */
+
+PR_IMPLEMENT(PRCondVar*) PR_NewCondVar(PRLock *lock)
+{
+    PRCondVar *cv = PR_NEW(PRCondVar);
+    PR_ASSERT(lock != NULL);
+    if (cv != NULL)
+    {
+	cv->cv = condition_alloc();
+	if (cv->cv == NULL)
+	{
+		PR_DELETE(cv);
+		return NULL;
+	}
+	condition_init(cv->cv);
+        cv->lock = lock;
+    }
+    return cv;
+}  /* PR_NewCondVar */
+
+PR_IMPLEMENT(void) PR_DestroyCondVar(PRCondVar *cvar)
+{
+    condition_free(cvar->cv);
+#if defined(DEBUG)
+        memset(cvar, 0xaf, sizeof(PRCondVar));
+#endif
+    PR_DELETE(cvar);
+}  /* PR_DestroyCondVar */
+
+PR_IMPLEMENT(PRStatus) PR_WaitCondVar(PRCondVar *cvar, PRIntervalTime timeout)
+{
+#if 0
+    PRStatus rv;
+#endif
+    PRThread *thred = PR_CurrentThread();
+
+    PR_ASSERT(cvar != NULL);
+    /* We'd better be locked */
+    PR_ASSERT(CTHREAD_MUTEX_IS_LOCKED(cvar->lock->mutex));
+    /* and it better be by us */
+    PR_ASSERT(cvar->lock->owner == cthread_self());
+
+    /*
+     * The thread waiting is used for PR_Interrupt
+     */
+    thred->waiting = cvar;  /* this is where we're waiting */
+
+    if (thred->state & CT_THREAD_ABORTED)
+    {
+        thred->waiting = NULL;
+        PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
+        thred->state &= ~CT_THREAD_ABORTED;
+        return PR_FAILURE;
+    }
+
+    /*
+     * If we have pending notifies, post them now.
+     *
+     * This is not optimal. We're going to post these notifies
+     * while we're holding the lock. That means on MP systems
+     * that they are going to collide for the lock that we will
+     * hold until we actually wait.
+     */
+    if (0 != cvar->lock->notified.length)
+        pt_PostNotifies(cvar->lock, PR_FALSE);
+
+    /*
+     * We're surrendering the lock, so clear out the owner field.
+     */
+    CTHREAD_ZERO_THR_HANDLE(cvar->lock->owner);
+
+#if 0 /* XXX FIX ME */
+    if (timeout == PR_INTERVAL_NO_TIMEOUT)
+#endif
+        condition_wait(cvar->cv, cvar->lock->mutex);
+#if 0
+    else
+        rv = ct_TimedWait(&cvar->cv, &cvar->lock->mutex, timeout);
+#endif
+
+    /* We just got the lock back - this better be empty */
+    PR_ASSERT(CTHREAD_THR_HANDLE_IS_ZERO(cvar->lock->owner));
+    CTHREAD_COPY_THR_HANDLE(cthread_self(), cvar->lock->owner);
+
+    PR_ASSERT(0 == cvar->lock->notified.length);
+    thred->waiting = NULL;  /* and now we're not */
+    if (thred->state & CT_THREAD_ABORTED)
+    {
+        PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
+        thred->state &= ~CT_THREAD_ABORTED;
+        return PR_FAILURE;
+    }
+#if 0
+    return (rv == 0) ? PR_SUCCESS : PR_FAILURE;
+#else
+    return PR_SUCCESS;
+#endif
+}  /* PR_WaitCondVar */
+
+PR_IMPLEMENT(PRStatus) PR_NotifyCondVar(PRCondVar *cvar)
+{
+    PR_ASSERT(cvar != NULL);   
+    ct_PostNotifyToCvar(cvar, PR_FALSE);
+    return PR_SUCCESS;
+}  /* PR_NotifyCondVar */
+
+PR_IMPLEMENT(PRStatus) PR_NotifyAllCondVar(PRCondVar *cvar)
+{
+    PR_ASSERT(cvar != NULL);
+    ct_PostNotifyToCvar(cvar, PR_TRUE);
+    return PR_SUCCESS;
+}  /* PR_NotifyAllCondVar */
+
+/**************************************************************/
+/**************************************************************/
+/***************************MONITORS***************************/
+/**************************************************************/
+/**************************************************************/
+
+PR_IMPLEMENT(PRMonitor*) PR_NewMonitor(void)
+{
+    PRMonitor *ml;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    ml = PR_NEWZAP(PRMonitor);
+    if (ml != NULL)
+    {
+	ml->lock.mutex = mutex_alloc();
+	if (ml->lock.mutex == NULL)
+	{
+		PR_DELETE(ml);
+		return NULL;
+	}
+
+	ml->cvar.cv = condition_alloc();
+	if (ml->cvar.cv == NULL)
+	{
+		PR_DELETE(ml);
+		mutex_free(ml->lock.mutex);
+		return NULL;
+	}
+
+	mutex_init(ml->lock.mutex);
+	condition_init(ml->cvar.cv);
+
+        ml->entryCount = 0;
+        ml->cvar.lock = &ml->lock;
+    }
+    return ml;
+}  /* PR_NewMonitor */
+
+PR_IMPLEMENT(PRMonitor*) PR_NewNamedMonitor(const char* name)
+{
+    PRMonitor* mon = PR_NewMonitor();
+    mon->name = name;
+    return mon;
+}
+
+PR_IMPLEMENT(void) PR_DestroyMonitor(PRMonitor *mon)
+{
+    PR_ASSERT(mon != NULL);
+    condition_free(mon->cvar.cv);
+    mutex_free(mon->lock.mutex);
+#if defined(DEBUG)
+        memset(mon, 0xaf, sizeof(PRMonitor));
+#endif
+    PR_DELETE(mon);    
+}  /* PR_DestroyMonitor */
+
+
+/* The GC uses this; it is quite arguably a bad interface.  I'm just 
+ * duplicating it for now - XXXMB
+ */
+PR_IMPLEMENT(PRInt32) PR_GetMonitorEntryCount(PRMonitor *mon)
+{
+    cthread_t self = cthread_self();
+    if (mon->owner == self)
+        return mon->entryCount;
+    return 0;
+}
+
+PR_IMPLEMENT(void) PR_EnterMonitor(PRMonitor *mon)
+{
+    int rv;
+    cthread_t self = cthread_self();
+
+    PR_ASSERT(mon != NULL);
+    rv = mutex_try_lock(mon->lock.mutex);
+    if (CT_TRYLOCK_SUCCESS == rv)
+    {
+        /* I now have the lock - I can play in the sandbox */
+        /* could/should/would not have gotten lock if entries != 0 */
+        PR_ASSERT(0 == mon->entryCount);
+        PR_ASSERT(CTHREAD_THR_HANDLE_IS_ZERO(mon->lock.owner));
+    }
+    else
+    {
+        PR_ASSERT(CT_TRYLOCK_BUSY == rv);  /* and if it isn't? */
+        /* somebody has it locked - is it me? */
+        if (!mon->owner == self)
+        {
+            /* it's not me - this should block */
+            PR_Lock(&mon->lock);
+            /* and now I have the lock */
+            PR_ASSERT(0 == mon->entryCount);
+        }
+    }
+
+    mon->owner = self;
+    mon->lock.owner = self;
+    mon->entryCount += 1;
+
+}  /* PR_EnterMonitor */
+
+PR_IMPLEMENT(PRStatus) PR_ExitMonitor(PRMonitor *mon)
+{
+    cthread_t self = cthread_self();
+
+    PR_ASSERT(mon != NULL);
+    /* The lock better be that - locked */
+    PR_ASSERT(CTHREAD_MUTEX_IS_LOCKED(mon->lock.mutex));
+    /* we'd better be the owner */
+
+    PR_ASSERT(mon->owner == self);
+    if (!mon->owner == self)
+        return PR_FAILURE;
+
+    /* if it's locked and we have it, then the entries should be > 0 */
+    PR_ASSERT(mon->entryCount > 0);
+    mon->entryCount -= 1;  /* reduce by one */
+    PR_ASSERT(mon->entryCount >= 0);  /* doesn't seem like too much to ask */
+    if (mon->entryCount == 0)
+    {
+        /* and if it transitioned to zero - unlock */
+        CTHREAD_ZERO_THR_HANDLE(mon->owner);  /* make the owner unknown */
+        PR_Unlock(&mon->lock);
+    }
+    return PR_SUCCESS;
+}  /* PR_ExitMonitor */
+
+PR_IMPLEMENT(PRStatus) PR_Wait(PRMonitor *mon, PRIntervalTime timeout)
+{
+    PRStatus rv;
+    PRInt16 saved_entries;
+    cthread_t saved_owner;
+
+    PR_ASSERT(mon != NULL);
+    /* we'd better be locked */
+    PR_ASSERT(CTHREAD_MUTEX_IS_LOCKED(mon->lock.mutex));
+    /* and the entries better be positive */
+    PR_ASSERT(mon->entryCount > 0);
+    /* and it better be by us */
+    PR_ASSERT(mon->owner == cthread_self());
+
+    /* tuck these away 'till later */
+    saved_entries = mon->entryCount; 
+    mon->entryCount = 0;
+    CTHREAD_COPY_THR_HANDLE(mon->owner, saved_owner);
+    CTHREAD_ZERO_THR_HANDLE(mon->owner);
+    
+    rv = PR_WaitCondVar(&mon->cvar, timeout);
+
+    /* reinstate the intresting information */
+    mon->entryCount = saved_entries;
+    CTHREAD_COPY_THR_HANDLE(saved_owner, mon->owner);
+
+    return rv;
+}  /* PR_Wait */
+
+PR_IMPLEMENT(PRStatus) PR_Notify(PRMonitor *mon)
+{
+    PR_ASSERT(NULL != mon);
+    /* we'd better be locked */
+    PR_ASSERT(CTHREAD_MUTEX_IS_LOCKED(mon->lock.mutex));
+    /* and the entries better be positive */
+    PR_ASSERT(mon->entryCount > 0);
+    /* and it better be by us */
+    PR_ASSERT(mon->owner == cthread_self());
+
+    ct_PostNotifyToCvar(&mon->cvar, PR_FALSE);
+
+    return PR_SUCCESS;
+}  /* PR_Notify */
+
+PR_IMPLEMENT(PRStatus) PR_NotifyAll(PRMonitor *mon)
+{
+    PR_ASSERT(mon != NULL);
+    /* we'd better be locked */
+    PR_ASSERT(CTHREAD_MUTEX_IS_LOCKED(mon->lock.mutex));
+    /* and the entries better be positive */
+    PR_ASSERT(mon->entryCount > 0);
+    /* and it better be by us */
+    PR_ASSERT(mon->owner == cthread_self());
+
+    ct_PostNotifyToCvar(&mon->cvar, PR_TRUE);
+
+    return PR_SUCCESS;
+}  /* PR_NotifyAll */
+
+/**************************************************************/
+/**************************************************************/
+/**************************SEMAPHORES**************************/
+/**************************************************************/
+/**************************************************************/
+PR_IMPLEMENT(void) PR_PostSem(PRSemaphore *semaphore)
+{
+	PR_Lock(semaphore->cvar->lock);
+	PR_NotifyCondVar(semaphore->cvar);
+	semaphore->count += 1;
+	PR_Unlock(semaphore->cvar->lock);
+}  /* PR_PostSem */
+
+PR_IMPLEMENT(PRStatus) PR_WaitSem(PRSemaphore *semaphore)
+{
+	PRStatus status = PR_SUCCESS;
+	PR_Lock(semaphore->cvar->lock);
+	while ((semaphore->count == 0) && (PR_SUCCESS == status))
+		status = PR_WaitCondVar(semaphore->cvar, PR_INTERVAL_NO_TIMEOUT);
+	if (PR_SUCCESS == status) semaphore->count -= 1;
+	PR_Unlock(semaphore->cvar->lock);
+	return status;
+}  /* PR_WaitSem */
+
+PR_IMPLEMENT(void) PR_DestroySem(PRSemaphore *semaphore)
+{
+    PR_DestroyLock(semaphore->cvar->lock);
+    PR_DestroyCondVar(semaphore->cvar);
+    PR_DELETE(semaphore);
+}  /* PR_DestroySem */
+
+PR_IMPLEMENT(PRSemaphore*) PR_NewSem(PRUintn value)
+{
+    PRSemaphore *semaphore;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    semaphore = PR_NEWZAP(PRSemaphore);
+    if (NULL != semaphore)
+    {
+        PRLock *lock = PR_NewLock();
+        if (NULL != lock)
+        {
+            semaphore->cvar = PR_NewCondVar(lock);
+            if (NULL != semaphore->cvar)
+            {
+                semaphore->count = value;
+                return semaphore;
+            }
+            PR_DestroyLock(lock);
+        }
+        PR_DELETE(semaphore);
+    }
+    return NULL;
+}
+
+/**************************************************************/
+/**************************************************************/
+/******************ROUTINES FOR DCE EMULATION******************/
+/**************************************************************/
+/**************************************************************/
+
+#include "prpdce.h"
+
+PR_IMPLEMENT(PRStatus) PRP_TryLock(PRLock *lock)
+{
+    PRIntn rv = mutex_try_lock(lock->mutex);
+    if (rv == CT_TRYLOCK_SUCCESS)
+    {
+        PR_ASSERT(CTHREAD_THR_HANDLE_IS_ZERO(lock->owner));
+        CTHREAD_COPY_THR_HANDLE(cthread_self(), lock->owner); 
+    }
+    else
+        PR_ASSERT(!CTHREAD_THR_HANDLE_IS_ZERO(lock->owner));
+    /* XXX set error code? */
+    return (CT_TRYLOCK_SUCCESS == rv) ? PR_SUCCESS : PR_FAILURE;
+}  /* PRP_TryLock */
+
+PR_IMPLEMENT(PRCondVar*) PRP_NewNakedCondVar()
+{
+    PRCondVar *cv;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    cv = PR_NEW(PRCondVar);
+    if (cv != NULL)
+    {
+	cv->cv = condition_alloc();
+	if (cv->cv == NULL)
+	{
+		PR_DELETE(cv);
+		return NULL;
+	}
+
+	condition_init(cv->cv);
+    }
+    return cv;
+}  /* PRP_NewNakedCondVar */
+
+PR_IMPLEMENT(void) PRP_DestroyNakedCondVar(PRCondVar *cvar)
+{
+    condition_free(cvar->cv);
+#if defined(DEBUG)
+        memset(cvar, 0xaf, sizeof(PRCondVar));
+#endif
+    PR_DELETE(cvar);
+}  /* PRP_DestroyNakedCondVar */
+
+PR_IMPLEMENT(PRStatus) PRP_NakedWait(
+    PRCondVar *cvar, PRLock *ml, PRIntervalTime timeout)
+{
+#if 0
+    PRStatus rv;
+#endif
+    PR_ASSERT(cvar != NULL);
+    /* XXX do we really want to assert this in a naked wait? */
+    PR_ASSERT(CTHREAD_MUTEX_IS_LOCKED(ml->mutex));
+#if 0 /* XXX fix me -- toshok */
+    if (timeout == PR_INTERVAL_NO_TIMEOUT)
+#endif
+        condition_wait(cvar->cv, ml->mutex);
+#if 0
+    else
+        rv = pt_TimedWait(&cvar->cv, &ml->mutex, timeout);
+#endif
+#if 0
+    return (rv == 0) ? PR_SUCCESS : PR_FAILURE;
+#else
+    return PR_SUCCESS;
+#endif
+}  /* PRP_NakedWait */
+
+PR_IMPLEMENT(PRStatus) PRP_NakedNotify(PRCondVar *cvar)
+{
+    PR_ASSERT(cvar != NULL);
+    condition_signal(cvar->cv);
+    return PR_SUCCESS;
+}  /* PRP_NakedNotify */
+
+PR_IMPLEMENT(PRStatus) PRP_NakedBroadcast(PRCondVar *cvar)
+{
+    PR_ASSERT(cvar != NULL);
+    condition_broadcast(cvar->cv);
+    return PR_SUCCESS;
+}  /* PRP_NakedBroadcast */
+
+#endif  /* defined(_PR_CTHREADS) */
+
+/* ptsynch.c */
new file mode 100644
--- /dev/null
+++ b/pr/src/cthreads/ctthread.c
@@ -0,0 +1,1031 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * The contents of this file are subject to the Netscape Public License
+ * Version 1.0 (the "NPL"); you may not use this file except in
+ * compliance with the NPL.  You may obtain a copy of the NPL at
+ * http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the NPL is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
+ * for the specific language governing rights and limitations under the
+ * NPL.
+ *
+ * The Initial Developer of this code under the NPL is Netscape
+ * Communications Corporation.  Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
+ * Reserved.
+ */
+
+/*
+** File:            ctthread.c
+** Descritpion:        Implemenation for threds using cthreads
+** Exports:            ctthread.h
+*/
+
+#if defined(_PR_CTHREADS)
+
+#include "prlog.h"
+#include "primpl.h"
+#include "prpdce.h"
+
+#include <mach/cthreads.h>
+#include <mach/kern_return.h>
+#include <string.h>
+#include <signal.h>
+
+/*
+ * Record whether or not we have the privilege to set the scheduling
+ * policy and priority of threads.  0 means that privilege is available.
+ * EPERM means that privilege is not available.
+ */
+
+PRIntn pt_schedpriv;
+
+struct _CT_Bookeeping ct_book = {0};
+
+static void init_cthread_gc_support(void);
+
+static PRIntn ct_PriorityMap(PRThreadPriority pri)
+{
+    return ct_book.minPrio +
+	    pri * (ct_book.maxPrio - ct_book.minPrio) / PR_PRIORITY_LAST;
+}
+
+/*
+** Initialize a stack for a native cthread thread
+*/
+static void _PR_InitializeStack(PRThreadStack *ts)
+{
+    if( ts && (ts->stackTop == 0) ) {
+        ts->allocBase = (char *) &ts;
+        ts->allocSize = ts->stackSize;
+
+        /*
+        ** Setup stackTop and stackBottom values.
+        */
+#ifdef HAVE_STACK_GROWING_UP
+        ts->stackBottom = ts->allocBase + ts->stackSize;
+        ts->stackTop = ts->allocBase;
+#else
+        ts->stackTop    = ts->allocBase;
+        ts->stackBottom = ts->allocBase - ts->stackSize;
+#endif
+    }
+}
+
+static void *_ct_root(void *arg)
+{
+    int rv;
+    PRThread *thred = (PRThread*)arg;
+    PRBool detached = (thred->state & CT_THREAD_DETACHED);
+
+    /*
+     * Both the parent thread and this new thread set thred->id.
+     * The new thread must ensure that thred->id is set before
+     * it executes its startFunc.  The parent thread must ensure
+     * that thred->id is set before PR_CreateThread() returns.
+     * Both threads set thred->id without holding a lock.  Since
+     * they are writing the same value, this unprotected double
+     * write should be safe.
+     */
+    thred->id = cthread_self();
+
+    /*
+    ** CThreads can't detach during creation, so do it late.
+    ** I would like to do it only here, but that doesn't seem
+    ** to work.
+    */
+    if (thred->state & CT_THREAD_DETACHED)
+    {
+        /* cthread_detach() might modify its argument, so we must pass a copy */
+        cthread_t self = thred->id;
+        cthread_detach(self);
+    }
+
+    /* Set up the thread stack information */
+    _PR_InitializeStack(thred->stack);
+
+    /*
+     * Set within the current thread the pointer to our object.
+     * This object will be deleted when the thread termintates,
+     * whether in a join or detached (see _PR_InitThreads()).
+     */
+    rv = cthread_setspecific(ct_book.key, thred);
+    PR_ASSERT(0 == rv);
+
+    /* make the thread visible to the rest of the runtime */
+    PR_Lock(ct_book.ml);
+
+    /* If this is a GCABLE thread, set its state appropriately */
+    if (thred->suspend & CT_THREAD_SETGCABLE)
+	    thred->state |= CT_THREAD_GCABLE;
+    thred->suspend = 0;
+
+    thred->prev = ct_book.last;
+    ct_book.last->next = thred;
+    thred->next = NULL;
+    ct_book.last = thred;
+    PR_Unlock(ct_book.ml);
+
+    thred->startFunc(thred->arg);  /* make visible to the client */
+
+    /* unhook the thread from the runtime */
+    PR_Lock(ct_book.ml);
+    if (thred->state & CT_THREAD_SYSTEM)
+        ct_book.system -= 1;
+    else if (--ct_book.user == ct_book.this_many)
+        PR_NotifyAllCondVar(ct_book.cv);
+    thred->prev->next = thred->next;
+    if (NULL == thred->next)
+        ct_book.last = thred->prev;
+    else
+        thred->next->prev = thred->prev;
+
+    /*
+     * At this moment, PR_CreateThread() may not have set thred->id yet.
+     * It is safe for a detached thread to free thred only after
+     * PR_CreateThread() has set thred->id.
+     */
+    if (detached)
+    {
+        while (!thred->okToDelete)
+            PR_WaitCondVar(ct_book.cv, PR_INTERVAL_NO_TIMEOUT);
+    }
+    PR_Unlock(ct_book.ml);
+
+    /* last chance to delete this puppy if the thread is detached */
+    if (detached)
+    {
+    	PR_DELETE(thred->stack);
+        memset(thred, 0xaf, sizeof(PRThread));
+        PR_DELETE(thred);
+    }
+
+    return NULL;
+}  /* _ct_root */
+
+static PRThread* _PR_CreateThread(
+    PRThreadType type, void (*start)(void *arg),
+    void *arg, PRThreadPriority priority, PRThreadScope scope,
+    PRThreadState state, PRUint32 stackSize, PRBool isGCAble)
+{
+    int rv;
+    PRThread *thred;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    if ((PRIntn)PR_PRIORITY_FIRST > (PRIntn)priority)
+        priority = PR_PRIORITY_FIRST;
+    else if ((PRIntn)PR_PRIORITY_LAST < (PRIntn)priority)
+        priority = PR_PRIORITY_LAST;
+    
+#if defined(IRIX)
+    if ((16 * 1024) > stackSize) stackSize = (16 * 1024);  /* IRIX minimum */
+    else
+#endif
+    if (0 == stackSize) stackSize = (64 * 1024);  /* default == 64K */
+    /*
+     * Linux doesn't have pthread_attr_setstacksize.
+     */
+
+    thred = PR_NEWZAP(PRThread);
+    if (thred != NULL)
+    {
+        thred->arg = arg;
+        thred->startFunc = start;
+        thred->priority = priority;
+        if (PR_UNJOINABLE_THREAD == state)
+            thred->state |= CT_THREAD_DETACHED;
+        if (PR_GLOBAL_THREAD == scope)
+            thred->state |= CT_THREAD_GLOBAL;
+        if (PR_SYSTEM_THREAD == type)
+            thred->state |= CT_THREAD_SYSTEM;
+
+        thred->suspend =(isGCAble) ? CT_THREAD_SETGCABLE : 0;
+
+        thred->stack = PR_NEWZAP(PRThreadStack);
+        if (thred->stack == NULL) {
+            PR_DELETE(thred);  /* all that work ... poof! */
+            thred = NULL;  /* and for what? */
+            goto done;
+        }
+        thred->stack->stackSize = stackSize;
+        thred->stack->thr = thred;
+
+#ifdef CT_NO_SIGTIMEDWAIT
+      thred->suspendResumeMutex = mutex_alloc();
+      if (thred->suspendResumeMutex)
+	{
+	  mutex_init(thred->suspendResumeMutex);
+	}
+      thred->suspendResumeCV = condition_alloc();
+      if (thred->suspendResumeCV)
+	{
+	  condition_init(thred->suspendResumeCV);
+	}
+#endif
+
+	    /* make the thread counted to the rest of the runtime */
+	    PR_Lock(ct_book.ml);
+	    if (thred->state & CT_THREAD_SYSTEM)
+	        ct_book.system += 1;
+	    else ct_book.user += 1;
+	    PR_Unlock(ct_book.ml);
+
+        if ((thred->id = cthread_fork(_ct_root, thred)) == 0)
+        {
+            PR_Lock(ct_book.ml);
+            if (thred->state & CT_THREAD_SYSTEM)
+                ct_book.system -= 1;
+            else if (--ct_book.user == ct_book.this_many)
+                PR_NotifyAllCondVar(ct_book.cv);
+            PR_Unlock(ct_book.ml);
+
+            PR_DELETE(thred->stack);
+            PR_DELETE(thred);  /* all that work ... poof! */
+            thred = NULL;  /* and for what? */
+            goto done;
+        }
+
+        /*
+         * If the new thread is detached, tell it that PR_CreateThread()
+         * has set thred->id so it's ok to delete thred.
+         */
+        if (PR_UNJOINABLE_THREAD == state)
+        {
+            PR_Lock(ct_book.ml);
+            thred->okToDelete = PR_TRUE;
+            PR_NotifyAllCondVar(ct_book.cv);
+            PR_Unlock(ct_book.ml);
+        }
+    }
+
+done:
+    return thred;
+}  /* _PR_CreateThread */
+
+PR_IMPLEMENT(PRThread*) PR_CreateThread(
+    PRThreadType type, void (*start)(void *arg), void *arg,
+    PRThreadPriority priority, PRThreadScope scope,
+    PRThreadState state, PRUint32 stackSize)
+{
+    return _PR_CreateThread(
+        type, start, arg, priority, scope, state, stackSize, PR_FALSE);
+} /* PR_CreateThread */
+
+PR_IMPLEMENT(PRThread*) PR_CreateThreadGCAble(
+    PRThreadType type, void (*start)(void *arg), void *arg, 
+    PRThreadPriority priority, PRThreadScope scope,
+    PRThreadState state, PRUint32 stackSize)
+{
+    return _PR_CreateThread(
+        type, start, arg, priority, scope, state, stackSize, PR_TRUE);
+}  /* PR_CreateThreadGCAble */
+
+PR_IMPLEMENT(void*) GetExecutionEnvironment(PRThread *thred)
+{
+    return thred->environment;
+}  /* GetExecutionEnvironment */
+ 
+PR_IMPLEMENT(void) SetExecutionEnvironment(PRThread *thred, void *env)
+{
+    thred->environment = env;
+}  /* SetExecutionEnvironment */
+
+PR_IMPLEMENT(PRThread*) PR_AttachThread(
+    PRThreadType type, PRThreadPriority priority, PRThreadStack *stack)
+{
+    PRThread *thred = NULL;
+    void *privateData = NULL;
+
+    /*
+     * NSPR must have been initialized when PR_AttachThread is called.
+     * We cannot have PR_AttachThread call implicit initialization
+     * because if multiple threads call PR_AttachThread simultaneously,
+     * NSPR may be initialized more than once.
+     */
+    if (!_pr_initialized) {
+        /* Since NSPR is not initialized, we cannot call PR_SetError. */
+        return NULL;
+    }
+
+    cthread_getspecific(ct_book.key, &privateData);
+    if (NULL != privateData)
+    {
+        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
+        return NULL;
+    }
+    thred = PR_NEWZAP(PRThread);
+    if (NULL == thred) PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
+    else
+    {
+        int rv;
+
+        if ((PRIntn)PR_PRIORITY_FIRST > (PRIntn)priority)
+            priority = PR_PRIORITY_FIRST;
+        else if ((PRIntn)PR_PRIORITY_LAST < (PRIntn)priority)
+            priority = PR_PRIORITY_LAST;
+
+        thred->priority = priority;
+        thred->id = cthread_self();
+        rv = cthread_setspecific(ct_book.key, thred);
+        PR_ASSERT(0 == rv);
+
+        PR_Lock(ct_book.ml);
+        if (PR_SYSTEM_THREAD == type)
+        {
+            ct_book.system += 1;
+            thred->state |= CT_THREAD_SYSTEM;
+        }
+        else ct_book.user += 1;
+
+        /* then put it into the list */
+        thred->prev = ct_book.last;
+	    ct_book.last->next = thred;
+        thred->next = NULL;
+        ct_book.last = thred;
+        PR_Unlock(ct_book.ml);
+
+    }
+    return thred;  /* may be NULL */
+}  /* PR_AttachThread */
+
+PR_IMPLEMENT(PRStatus) PR_JoinThread(PRThread *thred)
+{
+    int rv = -1;
+    void *result = NULL;
+    PR_ASSERT(thred != NULL);
+
+    if ((0xafafafaf == thred->state)
+    || (CT_THREAD_DETACHED & thred->state))
+    {
+        /*
+         * This might be a bad address, but if it isn't, the state should
+         * either be an unjoinable thread or it's already had the object
+         * deleted. However, the client that called join on a detached
+         * thread deserves all the rath I can muster....
+         */
+        PR_SetError(PR_ILLEGAL_ACCESS_ERROR, 0);
+        PR_LogPrint(
+            "PR_JoinThread: 0x%X not joinable | already smashed\n", thred);
+
+        PR_ASSERT((0xafafafaf == thred->state)
+        || (CT_THREAD_DETACHED & thred->state));
+    }
+    else
+    {
+        cthread_t id = thred->id;
+        result = cthread_join(id);
+    	PR_DELETE(thred->stack);
+        memset(thred, 0xaf, sizeof(PRThread));
+        PR_ASSERT(result == NULL);
+        PR_DELETE(thred);
+    }
+    return (0 == rv) ? PR_SUCCESS : PR_FAILURE;
+}  /* PR_JoinThread */
+
+PR_IMPLEMENT(void) PR_DetachThread()
+{
+    PRThread *thred;
+    cthread_getspecific(ct_book.key, &thred);
+    PR_ASSERT(NULL != thred);
+
+    if (NULL != thred)
+    {
+	int rv;
+
+        PR_Lock(ct_book.ml);
+        if (thred->state & CT_THREAD_SYSTEM)
+            ct_book.system -= 1;
+        else if (--ct_book.user == ct_book.this_many)
+            PR_NotifyAllCondVar(ct_book.cv);
+        thred->prev->next = thred->next;
+        if (NULL == thred->next)
+            ct_book.last = thred->prev;
+        else
+            thred->next->prev = thred->prev;
+        PR_Unlock(ct_book.ml);
+
+        rv = cthread_setspecific(ct_book.key, NULL);
+	PR_ASSERT(0 == rv);
+    	PR_DELETE(thred->stack);
+        memset(thred, 0xaf, sizeof(PRThread));
+        PR_DELETE(thred);
+    }    
+}  /* PR_DetachThread */
+
+PR_IMPLEMENT(PRThread*) PR_GetCurrentThread()
+{
+    PRThread *thred;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    cthread_getspecific(ct_book.key, &thred);
+    PR_ASSERT(NULL != thred);
+    return thred;
+}  /* PR_GetCurrentThread */
+
+PR_IMPLEMENT(PRThreadScope) PR_GetThreadScope(const PRThread *thred)
+{
+    return (thred->state & CT_THREAD_GLOBAL) ?
+        PR_GLOBAL_THREAD : PR_LOCAL_THREAD;
+}  /* PR_GetThreadScope() */
+
+PR_IMPLEMENT(PRThreadType) PR_GetThreadType(const PRThread *thred)
+{
+    return (thred->state & CT_THREAD_SYSTEM) ?
+        PR_SYSTEM_THREAD : PR_USER_THREAD;
+}
+
+PR_IMPLEMENT(PRThreadState) PR_GetThreadState(const PRThread *thred)
+{
+    return (thred->state & CT_THREAD_DETACHED) ?
+        PR_UNJOINABLE_THREAD : PR_JOINABLE_THREAD;
+}  /* PR_GetThreadState */
+
+PR_IMPLEMENT(PRThreadPriority) PR_GetThreadPriority(const PRThread *thred)
+{
+    PR_ASSERT(thred != NULL);
+    return thred->priority;
+}  /* PR_GetThreadPriority */
+
+PR_IMPLEMENT(void) PR_SetThreadPriority(PRThread *thred, PRThreadPriority newPri)
+{
+    int rv;
+
+    PR_ASSERT(NULL != thred);
+
+    if ((PRIntn)PR_PRIORITY_FIRST > (PRIntn)newPri)
+        newPri = PR_PRIORITY_FIRST;
+    else if ((PRIntn)PR_PRIORITY_LAST < (PRIntn)newPri)
+        newPri = PR_PRIORITY_LAST;
+
+    rv = cthread_priority(thred->id, ct_PriorityMap(newPri), 0);
+    PR_ASSERT(KERN_SUCCESS == rv);
+
+    thred->priority = newPri;
+}  /* PR_SetThreadPriority */
+
+PR_IMPLEMENT(PRStatus) PR_NewThreadPrivateIndex(
+    PRUintn *newIndex, PRThreadPrivateDTOR destructor)
+{
+    int rv;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    rv = cthread_key_create((cthread_key_t*)newIndex, destructor);
+
+    if (0 == rv)
+    {
+        PR_Lock(ct_book.ml);
+        if (*newIndex >= ct_book.highwater)
+            ct_book.highwater = *newIndex + 1;
+        PR_Unlock(ct_book.ml);
+        return PR_SUCCESS;
+    }
+    PR_SetError(PR_UNKNOWN_ERROR, rv);
+    return PR_FAILURE;
+}  /* PR_NewThreadPrivateIndex */
+
+PR_IMPLEMENT(PRStatus) PR_SetThreadPrivate(PRUintn index, void *priv)
+{
+    PRIntn rv;
+    if ((cthread_key_t)index >= ct_book.highwater)
+    {
+        PR_SetError(PR_TPD_RANGE_ERROR, 0);
+        return PR_FAILURE;
+    }
+    rv = cthread_setspecific((cthread_key_t)index, priv);
+    PR_ASSERT(0 == rv);
+    if (0 == rv) return PR_SUCCESS;
+
+    PR_SetError(PR_UNKNOWN_ERROR, rv);
+    return PR_FAILURE;
+}  /* PR_SetThreadPrivate */
+
+PR_IMPLEMENT(void*) PR_GetThreadPrivate(PRUintn index)
+{
+    void *result = NULL;
+    if ((cthread_key_t)index < ct_book.highwater)
+        cthread_getspecific((cthread_key_t)index, &result);
+    return result;
+}  /* PR_GetThreadPrivate */
+
+PR_IMPLEMENT(PRStatus) PR_Interrupt(PRThread *thred)
+{
+    /*
+    ** If the target thread indicates that it's waiting,
+    ** find the condition and broadcast to it. Broadcast
+    ** since we don't know which thread (if there are more
+    ** than one). This sounds risky, but clients must
+    ** test their invariants when resumed from a wait and
+    ** I don't expect very many threads to be waiting on
+    ** a single condition and I don't expect interrupt to
+    ** be used very often.
+    */
+    PRCondVar *victim;
+    PR_ASSERT(thred != NULL);
+    thred->state |= CT_THREAD_ABORTED;
+    victim = thred->waiting;
+    if (NULL != victim)
+    {
+        PRBool haveLock = (victim->lock->owner == cthread_self());
+        if (!haveLock) PR_Lock(victim->lock);
+        PR_NotifyAllCondVar(victim);
+        if (!haveLock) PR_Unlock(victim->lock);
+    }
+    return PR_SUCCESS;
+}  /* PR_Interrupt */
+
+PR_IMPLEMENT(void) PR_ClearInterrupt()
+{
+    PRThread *me = PR_CurrentThread();
+    me->state &= ~CT_THREAD_ABORTED;
+}  /* PR_ClearInterrupt */
+
+PR_IMPLEMENT(PRStatus) PR_Sleep(PRIntervalTime ticks)
+{
+    PRStatus rv;
+
+    if (!_pr_initialized) _PR_ImplicitInitialization();
+
+    if (PR_INTERVAL_NO_WAIT == ticks)
+    {
+        cthread_yield();
+        rv = PR_SUCCESS;
+    }
+    else
+    {
+        PRCondVar *cv = PR_NewCondVar(ct_book.ml);
+        PR_ASSERT(cv != NULL);
+        PR_Lock(ct_book.ml);
+        rv = PR_WaitCondVar(cv, ticks);
+        PR_Unlock(ct_book.ml);
+        PR_DestroyCondVar(cv);
+    }
+    return rv;
+}  /* PR_Sleep */
+
+PR_IMPLEMENT(void) _PR_InitThreads(
+    PRThreadType type, PRThreadPriority priority, PRUintn maxPTDs)
+{
+    int rv;
+    PRThread *thred;
+
+    /*
+    ** These might be function evaluations
+    */
+    ct_book.minPrio = CT_PRIO_MIN;
+    ct_book.maxPrio = CT_PRIO_MAX;
+    
+    PR_ASSERT(NULL == ct_book.ml);
+    ct_book.ml = PR_NewLock();
+    PR_ASSERT(NULL != ct_book.ml);
+    ct_book.cv = PR_NewCondVar(ct_book.ml);
+    PR_ASSERT(NULL != ct_book.cv);
+    thred = PR_NEWZAP(PRThread);
+    PR_ASSERT(NULL != thred);
+    thred->arg = NULL;
+    thred->startFunc = NULL;
+    thred->priority = priority;
+    thred->id = cthread_self();
+
+    thred->state |= (CT_THREAD_DETACHED | CT_THREAD_PRIMORD);
+    if (PR_SYSTEM_THREAD == type)
+    {
+        thred->state |= CT_THREAD_SYSTEM;
+        ct_book.system += 1;
+	    ct_book.this_many = 0;
+    }
+    else
+    {
+	    ct_book.user += 1;
+	    ct_book.this_many = 1;
+    }
+    thred->next = thred->prev = NULL;
+    ct_book.first = ct_book.last = thred;
+
+    thred->stack = PR_NEWZAP(PRThreadStack);
+    PR_ASSERT(thred->stack != NULL);
+    thred->stack->stackSize = 0;
+    thred->stack->thr = thred;
+	_PR_InitializeStack(thred->stack);
+
+    /*
+     * Create a key for our use to store a backpointer in the pthread
+     * to our PRThread object. This object gets deleted when the thread
+     * returns from its root in the case of a detached thread. Other
+     * threads delete the objects in Join.
+     *
+     * NB: The destructor logic seems to have a bug so it isn't used.
+     */
+    rv = cthread_key_create(&ct_book.key, NULL);
+    PR_ASSERT(0 == rv);
+    rv = cthread_setspecific(ct_book.key, thred);
+    PR_ASSERT(0 == rv);
+    
+    PR_SetThreadPriority(thred, priority);
+
+    init_cthread_gc_support();
+
+}  /* _PR_InitThreads */
+
+PR_IMPLEMENT(PRStatus) PR_Cleanup()
+{
+    PRThread *me = PR_CurrentThread();
+    PR_LOG(_pr_thread_lm, PR_LOG_MIN, ("PR_Cleanup: shutting down NSPR"));
+    PR_ASSERT(me->state & CT_THREAD_PRIMORD);
+    if (me->state & CT_THREAD_PRIMORD)
+    {
+        PR_Lock(ct_book.ml);
+        while (ct_book.user > ct_book.this_many)
+            PR_WaitCondVar(ct_book.cv, PR_INTERVAL_NO_TIMEOUT);
+        PR_Unlock(ct_book.ml);
+
+        /*
+         * I am not sure if it's safe to delete the cv and lock here,
+         * since there may still be "system" threads around.
+         */
+#if 0
+        PR_DestroyCondVar(ct_book.cv);
+        PR_DestroyLock(ct_book.ml);
+#endif
+        PR_DELETE(me->stack);
+        PR_DELETE(me);
+        return PR_SUCCESS;
+    }
+    return PR_FAILURE;
+}  /* PR_Cleanup */
+
+PR_IMPLEMENT(void) PR_ProcessExit(PRIntn status)
+{
+    _exit(status);
+}
+
+PR_IMPLEMENT(void) PR_DestroyThread(PRThread *thread)
+{
+    /* XXXMB - do we really want this? */
+    PR_ASSERT(!"IMPLEMENTED");
+    return;
+}
+
+/*
+ * $$$
+ * The following two thread-to-processor affinity functions are not
+ * yet implemented for cthreads.  By the way, these functions should return
+ * PRStatus rather than PRInt32 to indicate the success/failure status.
+ * $$$
+ */
+
+PR_IMPLEMENT(PRInt32) PR_GetThreadAffinityMask(PRThread *thread, PRUint32 *mask)
+{
+    return 0;  /* not implemented */
+}
+
+PR_IMPLEMENT(PRInt32) PR_SetThreadAffinityMask(PRThread *thread, PRUint32 mask )
+{
+    return 0;  /* not implemented */
+}
+
+PR_IMPLEMENT(void)
+PR_SetThreadDumpProc(PRThread* thread, PRThreadDumpProc dump, void *arg)
+{
+    thread->dump = dump;
+    thread->dumpArg = arg;
+}
+
+/* 
+ * Garbage collection support follows.
+ */
+
+/*
+ * statics for Garbage Collection support.  We don't need to protect these
+ * signal masks since the garbage collector itself is protected by a lock
+ * and multiple threads will not be garbage collecting at the same time.
+ */
+static sigset_t javagc_vtalarm_sigmask;
+static sigset_t javagc_intsoff_sigmask;
+
+static void init_cthread_gc_support()
+{
+    PRIntn rv;
+
+	rv = sigemptyset(&javagc_vtalarm_sigmask);
+    PR_ASSERT(0 == rv);
+	rv = sigaddset(&javagc_vtalarm_sigmask, SIGVTALRM);
+    PR_ASSERT(0 == rv);
+}
+
+PR_IMPLEMENT(void) PR_SetThreadGCAble()
+{
+    PR_Lock(ct_book.ml);
+	PR_CurrentThread()->state |= CT_THREAD_GCABLE;
+    PR_Unlock(ct_book.ml);
+}
+
+PR_IMPLEMENT(void) PR_ClearThreadGCAble()
+{
+    PR_Lock(ct_book.ml);
+	PR_CurrentThread()->state &= (~CT_THREAD_GCABLE);
+    PR_Unlock(ct_book.ml);
+}
+
+static PRBool suspendAllOn = PR_FALSE;
+static PRBool suspendAllSuspended = PR_FALSE;
+
+/* Are all GCAble threads (except gc'ing thread) suspended? */
+PR_IMPLEMENT(PRBool) PR_SuspendAllSuspended()
+{
+	return suspendAllSuspended;
+} /* PR_SuspendAllSuspended */
+
+PR_IMPLEMENT(PRStatus) PR_EnumerateThreads(PREnumerator func, void *arg)
+{
+    PRIntn count = 0;
+    PRStatus rv = PR_SUCCESS;
+    PRThread* thred = ct_book.first;
+    PRThread *me = PR_CurrentThread();
+
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, ("Begin PR_EnumerateThreads\n"));
+    /*
+     * $$$
+     * Need to suspend all threads other than me before doing this.
+     * This is really a gross and disgusting thing to do. The only
+     * good thing is that since all other threads are suspended, holding
+     * the lock during a callback seems like child's play.
+     * $$$
+     */
+    PR_ASSERT(suspendAllOn);
+
+    while (thred != NULL)
+    {
+        /* Steve Morse, 4-23-97: Note that we can't walk a queue by taking
+         * qp->next after applying the function "func".  In particular, "func"
+         * might remove the thread from the queue and put it into another one in
+         * which case qp->next no longer points to the next entry in the original
+         * queue.
+         *
+         * To get around this problem, we save qp->next in qp_next before applying
+         * "func" and use that saved value as the next value after applying "func".
+         */
+        PRThread* next = thred->next;
+
+        if (thred->state & CT_THREAD_GCABLE)
+        {
+            PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, 
+                   ("In PR_EnumerateThreads callback thread %X thid = %X\n", 
+                    thred, thred->id));
+
+            rv = func(thred, count++, arg);
+            if (rv != PR_SUCCESS)
+                return rv;
+        }
+        thred = next;
+    }
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, 
+	   ("End PR_EnumerateThreads count = %d \n", count));
+    return rv;
+}  /* PR_EnumerateThreads */
+
+/*
+ * PR_SuspendAll and PR_ResumeAll are called during garbage collection.  The strategy 
+ * we use is to send a SIGUSR2 signal to every gc able thread that we intend to suspend.
+ * The signal handler will record the stack pointer and will block until resumed by
+ * the resume call.  Since the signal handler is the last routine called for the
+ * suspended thread, the stack pointer will also serve as a place where all the
+ * registers have been saved on the stack for the previously executing routines.
+ *
+ * Through global variables, we also make sure that PR_Suspend and PR_Resume does not
+ * proceed until the thread is suspended or resumed.
+ */
+
+/*
+ * In the signal handler, we can not use condition variable notify or wait.
+ * This does not work consistently across all pthread platforms.  We also can not 
+ * use locking since that does not seem to work reliably across platforms.
+ * Only thing we can do is yielding while testing for a global condition
+ * to change.  This does work on pthread supported platforms.  We may have
+ * to play with priortities if there are any problems detected.
+ */
+
+ /* 
+  * In AIX, you cannot use ANY pthread calls in the signal handler except perhaps
+  * pthread_yield. But that is horribly inefficient. Hence we use only sigwait, no
+  * sigtimedwait is available. We need to use another user signal, SIGUSR1. Actually
+  * SIGUSR1 is also used by exec in Java. So our usage here breaks the exec in Java,
+  * for AIX. You cannot use pthread_cond_wait or pthread_delay_np in the signal
+  * handler as all synchronization mechanisms just break down. 
+  */
+
+static void PR_SuspendSet(PRThread *thred)
+{
+#if 0
+    PRIntn rv;
+
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, 
+	   ("PR_SuspendSet thred %X thread id = %X\n", thred, thred->id));
+
+
+    /*
+     * Check the thread state and signal the thread to suspend
+     */
+
+    PR_ASSERT((thred->suspend & CT_THREAD_SUSPENDED) == 0);
+
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, 
+	   ("doing pthread_kill in PR_SuspendSet thred %X tid = %X\n",
+	   thred, thred->id));
+    rv = pthread_kill (thred->id, SIGUSR2);
+    PR_ASSERT(0 == rv);
+#endif
+}
+
+static void PR_SuspendTest(PRThread *thred)
+{
+#if 0
+    PRIntn rv;
+
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, 
+	   ("Begin PR_SuspendTest thred %X thread id = %X\n", thred, thred->id));
+
+
+    /*
+     * Wait for the thread to be really suspended. This happens when the
+     * suspend signal handler stores the stack pointer and sets the state
+     * to suspended. 
+     */
+
+#if defined(CT_NO_SIGTIMEDWAIT)
+    pthread_mutex_lock(&thred->suspendResumeMutex);
+    while ((thred->suspend & CT_THREAD_SUSPENDED) == 0)
+    {
+	    pthread_cond_timedwait(
+	        &thred->suspendResumeCV, &thred->suspendResumeMutex, &onemillisec);
+	}
+	pthread_mutex_unlock(&thred->suspendResumeMutex);
+#else
+    while ((thred->suspend & CT_THREAD_SUSPENDED) == 0)
+    {
+		rv = sigtimedwait(&sigwait_set, NULL, &onemillisec);
+    	PR_ASSERT(-1 == rv);
+	}
+#endif
+
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS,
+        ("End PR_SuspendTest thred %X tid %X\n", thred, thred->id));
+#endif
+}  /* PR_SuspendTest */
+
+PR_IMPLEMENT(void) PR_ResumeSet(PRThread *thred)
+{
+#if 0
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, 
+	   ("PR_ResumeSet thred %X thread id = %X\n", thred, thred->id));
+
+    /*
+     * Clear the global state and set the thread state so that it will
+     * continue past yield loop in the suspend signal handler
+     */
+
+    PR_ASSERT(thred->suspend & CT_THREAD_SUSPENDED);
+
+
+    thred->suspend &= ~CT_THREAD_SUSPENDED;
+
+#if defined(CT_NO_SIGTIMEDWAIT)
+	pthread_kill(thred->id, SIGUSR1);
+#endif
+#endif
+}  /* PR_ResumeSet */
+
+PR_IMPLEMENT(void) PR_ResumeTest(PRThread *thred)
+{
+#if 0
+    PRIntn rv;
+
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, 
+	   ("Begin PR_ResumeTest thred %X thread id = %X\n", thred, thred->id));
+
+    /*
+     * Wait for the threads resume state to change
+     * to indicate it is really resumed 
+     */
+#if defined(CT_NO_SIGTIMEDWAIT)
+    pthread_mutex_lock(&thred->suspendResumeMutex);
+    while ((thred->suspend & CT_THREAD_RESUMED) == 0)
+    {
+	    pthread_cond_timedwait(
+	        &thred->suspendResumeCV, &thred->suspendResumeMutex, &onemillisec);
+    }
+    pthread_mutex_unlock(&thred->suspendResumeMutex);
+#else
+    while ((thred->suspend & CT_THREAD_RESUMED) == 0) {
+		rv = sigtimedwait(&sigwait_set, NULL, &onemillisec);
+    	PR_ASSERT(-1 == rv);
+	}
+#endif
+
+    thred->suspend &= ~CT_THREAD_RESUMED;
+
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, (
+        "End PR_ResumeTest thred %X tid %X\n", thred, thred->id));
+#endif
+}  /* PR_ResumeTest */
+
+PR_IMPLEMENT(void) PR_SuspendAll()
+{
+#if 0
+#ifdef DEBUG
+    PRIntervalTime stime, etime;
+#endif
+    PRThread* thred = ct_book.first;
+    PRThread *me = PR_CurrentThread();
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, ("Begin PR_SuspendAll\n"));
+    /*
+     * Stop all threads which are marked GC able.
+     */
+    PR_Lock(ct_book.ml);
+    suspendAllOn = 1;
+#ifdef DEBUG
+    stime = PR_IntervalNow();
+#endif
+    while (thred != NULL)
+    {
+	    if ((thred != me) && (thred->state & CT_THREAD_GCABLE))
+    		PR_SuspendSet(thred);
+        thred = thred->next;
+    }
+
+    /* Wait till they are really suspended */
+    thred = ct_book.first;
+    while (thred != NULL)
+    {
+	    if ((thred != me) && (thred->state & CT_THREAD_GCABLE))
+            PR_SuspendTest(thred);
+        thred = thred->next;
+    }
+
+    suspendAllSuspended = PR_TRUE;
+
+#ifdef DEBUG
+    etime = PR_IntervalNow();
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS,\
+        ("End PR_SuspendAll (time %dms)\n", etime - stime));
+#endif
+#endif
+}  /* PR_SuspendAll */
+
+PR_IMPLEMENT(void) PR_ResumeAll()
+{
+#if 0
+#ifdef DEBUG
+    PRIntervalTime stime, etime;
+#endif
+    PRThread* thred = ct_book.first;
+    PRThread *me = PR_CurrentThread();
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, ("Begin PR_ResumeAll\n"));
+    /*
+     * Resume all previously suspended GC able threads.
+     */
+    suspendAllSuspended = 0;
+#ifdef DEBUG
+    stime = PR_IntervalNow();
+#endif
+
+    while (thred != NULL)
+    {
+	    if ((thred != me) && (thred->state & CT_THREAD_GCABLE))
+    	    PR_ResumeSet(thred);
+        thred = thred->next;
+    }
+
+    thred = ct_book.first;
+    while (thred != NULL)
+    {
+	    if ((thred != me) && (thred->state & CT_THREAD_GCABLE))
+    	    PR_ResumeTest(thred);
+        thred = thred->next;
+    }
+
+    suspendAllOn = 0;
+    PR_Unlock(ct_book.ml);
+#ifdef DEBUG
+    etime = PR_IntervalNow();
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS,
+        ("End PR_ResumeAll (time %dms)\n", etime - stime));
+#endif
+}  /* PR_ResumeAll */
+
+/* Return the stack pointer for the given thread- used by the GC */
+PR_IMPLEMENT(void *)PR_GetSP(PRThread *thred)
+{
+    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, 
+	    ("in PR_GetSP thred %X thid = %X, sp = %X \n", 
+	    thred, thred->id, thred->sp));
+    return thred->sp;
+#endif
+}  /* PR_GetSP */
+
+#endif  /* defined(_PR_CTHREADS) */
+
+/* ptthread.c */