Milán Major

netscape-revival

1 branch
Code

nspr-/src/mdunix.c

#ifdef XP_UNIX
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include <stddef.h>
#include "gcint.h"
#include "mdint.h"
#include "prlog.h"
#include "prthread.h"
#include "prdump.h"
#include "prfile.h"
#include "prprf.h"

PR_LOG_DEFINE(IO);

#ifdef SW_THREADS
#include "swkern.h"
#endif

int _pr_no_clock;
PRMonitor *_pr_md_lock;

PRMonitor *_pr_select_lock;
PRMonitor *_pr_read_lock;
PRMonitor *_pr_write_lock;

PRCList _pr_ioq = PR_INIT_STATIC_CLIST(&_pr_ioq);

int64 _pr_lastTimePolled;
PRBool _pr_pollForIO;

static sigset_t timer_set;

#define LOCK_MD() PR_EnterMonitor(_pr_md_lock)
#define UNLOCK_MD() PR_ExitMonitor(_pr_md_lock)

#ifndef PROT_NONE
#define PROT_NONE 0
#endif

static int zero_fd = -1;

void _MD_OpenZero(void)
{
    /* Open /dev/zero if we haven't already */
    if (zero_fd == -1) {
#ifdef DEBUG
	/*
	** Disable using mmap(2) if NSPR_NO_MMAP is set
	*/
	if (getenv("NSPR_NO_MMAP")) {
	    zero_fd = -2;
	    return;
	}
#endif

	/*
	** Open /dev/zero and mmap in a hunk of memory which will
	** correspond to our maximum heap size
	*/
	zero_fd = open("/dev/zero", O_RDWR);
	if (zero_fd < 0) {
	    zero_fd = -2;
	}
    }
}

void *_MD_GrowGCHeap(uint32 *sizep)
{
    static void *lastaddr = (void*) _MD_GC_VMBASE;
    void *addr;
    size_t size;

    size = *sizep;

    LOCK_MD();
    if (zero_fd == -1) {
	_MD_OpenZero();
    }
    if (zero_fd == -2) {
	goto mmap_loses;
    }

    /* Extend the mapping */
    addr = mmap(lastaddr, size, PROT_READ|PROT_WRITE|PROT_EXEC,
#ifdef OSF1
		MAP_PRIVATE|MAP_FIXED,
#else
		MAP_PRIVATE,
#endif
		zero_fd, 0);
    if (addr == (void*)-1) {
        zero_fd = -2;
	goto mmap_loses;
    }
    lastaddr = ((char*)addr + size);
#ifdef DEBUG
    PR_LOG(GC, warn,
	   ("GC: heap extends from %08x to %08x\n",
	    _MD_GC_VMBASE,
	    _MD_GC_VMBASE + (char*)lastaddr - (char*)_MD_GC_VMBASE));
#endif
    UNLOCK_MD();
    return addr;

  mmap_loses:
    UNLOCK_MD();
    return malloc(size);
}

void _MD_FreeGCSegment(void *base, int32 len)
{
    if (zero_fd == -2) {
        free(base);
    } else {
        munmap(base, len);
    }
}

/************************************************************************/

/* List of free stack virtual memory chunks */
PRCList _pr_free_stacks = PR_INIT_STATIC_CLIST(&_pr_free_stacks);

/* How much space to leave between the stacks, at each end */
#define REDZONE		(2 << pr_pageShift)

#define THREAD_STACK_PTR(_qp) \
    ((PRThreadStack*) ((char*) (_qp) - offsetof(PRThreadStack,links)))

/*
** Allocate a stack for a thread.
*/
int _MD_NewStack(PRThread *thread, size_t stackSize)
{
    static char *lastaddr = (char*) _MD_STACK_VMBASE;
    char *p;
    PRCList *qp;
    PRThreadStack *ts = 0;
    int rv;

    /* Adjust stackSize. Round up to a page boundary */
    if (stackSize == 0) {
	stackSize = _MD_DEFAULT_STACK_SIZE;
    }
    stackSize = (stackSize + pr_pageSize - 1) >> pr_pageShift;
    stackSize <<= pr_pageShift;

    /*
    ** Open up the /dev/zero device if we haven't already done it. Some
    ** machines can't do it.
    */
    LOCK_MD();
    if (zero_fd == -1) {
	_MD_OpenZero();
    }
    if (zero_fd == -2) {
	UNLOCK_MD();
	goto mmap_loses;
    }

    /*
    ** Find a free thread stack. This searches the list of free'd up
    ** virtually mapped thread stacks.
    */
    qp = _pr_free_stacks.next;
    ts = 0;
    while (qp != &_pr_free_stacks) {
	ts = THREAD_STACK_PTR(qp);
	qp = qp->next;
	if ((ts->flags == 0) && ((ts->allocSize - 2*REDZONE) >= stackSize)) {
	    /* Found a stack that is not in use and is big enough */
	    PR_REMOVE_LINK(&ts->links);
	    ts->links.next = 0;
	    ts->links.prev = 0;
	    ts->flags = _PR_THREAD_STACK_BUSY;
	    break;
	}
	ts = 0;
    }
    UNLOCK_MD();

    if (!ts) {
	/* Make a new thread stack object. */
	ts = (PRThreadStack*) calloc(1, sizeof(PRThreadStack));
	if (!ts) {
	    return 0;
	}

	/*
	** Assign some of the virtual space to the new stack object. We
	** may not get that piece of VM, but if nothing else we will
	** advance the pointer so we don't collide (unless the OS screws
	** up).
	*/
	ts->allocBase = lastaddr;
	ts->allocSize = stackSize + 2*REDZONE;
	lastaddr = lastaddr + stackSize + 2*REDZONE;
    }

    /*
    ** Map the portion of the stack memory we are going to use, including
    ** the two redzone's. Don't map the entire allocSize, just the
    ** portion we are using this time around.
    */
    p = (char*) mmap(ts->allocBase, stackSize + 2*REDZONE,
		     PROT_READ|PROT_WRITE|PROT_EXEC,
#ifdef OSF1
		     MAP_PRIVATE|MAP_FIXED,
#else
		     MAP_PRIVATE,
#endif
		     zero_fd, 0);
    if (p == (char*)-1) {
	goto mmap_loses;
    }

    /*
    ** Make a redzone at both ends of the stack segment. Disallow access
    ** to those pages of memory. It's ok if the mprotect call's don't
    ** work - it just means that we don't really have a functional
    ** redzone.
    */
    ts->allocBase = p;
    rv = mprotect((void*)p, REDZONE, PROT_NONE);
    PR_ASSERT(rv >= 0);
    rv = mprotect((void*) (p + REDZONE + stackSize), REDZONE, PROT_NONE);
    PR_ASSERT(rv >= 0);
    ts->flags = _PR_THREAD_STACK_BUSY|_PR_THREAD_STACK_MAPPED;
    PR_LOG(THREAD, warn,
	   ("virtual thread stack: base=0x%x limit=0x%x bottom=0x%x top=0x%x\n",
	    ts->allocBase, ts->allocBase + ts->allocSize - 1,
	    ts->allocBase + REDZONE, ts->allocBase + REDZONE + stackSize - 1));
    goto done;

  mmap_loses:
    /*
    ** Use malloc to get the stack memory. This is the fallback code used
    ** on systems which:
    **
    ** 	(1) don't have a /dev/zero, or
    ** 	(2) have a random problem with mmaping.
    **
    ** When we create a stack this way, we can't get a proper redzone so
    ** we just pad the allocation.
    */
    if (!ts) {
	ts = (PRThreadStack*) calloc(1, sizeof(PRThreadStack));
	if (!ts) {
	    return 0;
	}
    }
    p = malloc(stackSize + 2*REDZONE);
    if (!p) {
	free(ts);
	return 0;
    }
    ts->allocBase = p;
    ts->allocSize = stackSize + 2*REDZONE;
    ts->flags = _PR_THREAD_STACK_BUSY;
#ifdef DEBUG
    /*
    ** Fill the redzone memory with a specific pattern so that we can
    ** check for it.
    */
    memset(p, 0xBF, REDZONE);
    memset(p + REDZONE + stackSize, 0xBF, REDZONE);
#endif
    PR_LOG(THREAD, warn,
	   ("malloc thread stack: base=0x%x limit=0x%x bottom=0x%x top=0x%x\n",
	    ts->allocBase, ts->allocBase + ts->allocSize - 1,
	    ts->allocBase + REDZONE, ts->allocBase + REDZONE + stackSize - 1));

  done:
    /*
    ** Setup stackTop and stackBottom values. Note that we are very
    ** careful to use the stackSize variable, NOT the ts->allocSize
    ** variable. The ts->allocSize variable represents the allocated size
    ** of the stack's VIRTUAL region, not the amount in use at the
    ** moment.
    */
    ts->stackSize = stackSize;
#ifdef HAVE_STACK_GROWING_UP
    ts->stackTop = ts->allocBase + REDZONE;
    ts->stackBottom = ts->stackTop + stackSize;
#else
    ts->stackBottom = ts->allocBase + REDZONE;
    ts->stackTop = ts->stackBottom + stackSize;
#endif
#if defined(DEBUG) && !defined(SOLARIS)
    /*
    ** Fill stack memory with something that turns into an illegal
    ** pointer value. This will sometimes find runtime references to
    ** uninitialized pointers. We don't do this for solaris because we
    ** can use purify instead.
    */
    if (ts->stackBottom < ts->stackTop) {
	memset(ts->stackBottom, 0xfe, stackSize);
    } else {
	memset(ts->stackTop, 0xfe, stackSize);
    }
#endif
    thread->stack = ts;
    return 1;
}

/*
** Free a stack for a thread
*/
void _MD_FreeStack(PRThread *thread)
{
    PRThreadStack *ts;

    ts = thread->stack;
    if (!ts) {
	return;
    }

    /* Zap thread->stack to prevent the GC from looking at a dead stack */
    thread->stack = 0;

    if (ts->flags & _PR_THREAD_STACK_MAPPED) {
	(void) munmap(ts->allocBase, ts->allocSize);
	LOCK_MD();
	PR_APPEND_LINK(&ts->links, &_pr_free_stacks);
	ts->flags = 0;
	UNLOCK_MD();
    } else {
	free(ts->allocBase);
	ts->allocBase = 0;
	free(ts);
    }
}

/************************************************************************/

void _MD_StartClock(void)
{
}

#ifdef DEBUG
void DumpStuffAndDie(void)
{
    PR_DumpStuff();
    abort();
}
#endif

#if (defined(IRIX) || defined(SOLARIS)) && defined(DEBUG)
#include <sys/resource.h>
#endif

void _MD_InitUnixOS(int when)
{
    if (when == _MD_INIT_AT_START) {
#ifdef DEBUG
	struct sigaction vtact;
	char *p;

	/*
	** Catch SIGQUIT's early so that we can interrupt what is going
	** on and find out thread status.
	*/
	vtact.sa_handler = (void (*)()) DumpStuffAndDie;
	vtact.sa_flags = 0;
	sigemptyset(&vtact.sa_mask);
	sigaddset(&vtact.sa_mask, SIGALRM);
	sigaction(SIGQUIT, &vtact, 0);

#if defined(IRIX) || defined(SOLARIS)
	p = getenv("PRVM_DATA");
	if (p && *p) {
	    struct rlimit lim;
	    int vlimit = atoi(p);
	    lim.rlim_max = lim.rlim_cur = vlimit * 1024 * 1024;
	    setrlimit(RLIMIT_DATA, &lim);
	}
	p = getenv("PRVM_STACK");
	if (p && *p) {
	    struct rlimit lim;
	    int vlimit = atoi(p);
	    lim.rlim_max = lim.rlim_cur = vlimit * 1024 * 1024;
	    setrlimit(RLIMIT_STACK, &lim);
	}
	p = getenv("PRVM_VM");
	if (p && *p) {
	    struct rlimit lim;
	    int vlimit = atoi(p);
	    lim.rlim_max = lim.rlim_cur = vlimit * 1024 * 1024;
	    setrlimit(RLIMIT_VMEM, &lim);
	}
#endif

#endif
	_pr_md_lock = PR_NewNamedMonitor(0, "md-lock");
    }
}

#ifndef HPUX
void PR_StartEvents(int usec)
{
#ifndef PURIFY
    struct sigaction vtact;
    struct itimerval itval;

    if (getenv("NSPR_NOCLOCK")) {
	_pr_no_clock = 1;
	return;
    }
    if (usec == 0) {
	usec = 50000;
    }

    vtact.sa_handler = (void (*)()) _PR_ClockInterruptHandler;
    vtact.sa_flags = SA_RESTART;
    sigfillset(&vtact.sa_mask);
    sigaction(SIGALRM, &vtact, 0);

    itval.it_interval.tv_sec = usec / 1000000;
    itval.it_interval.tv_usec = usec % 1000000;
    itval.it_value = itval.it_interval;
    if (setitimer(ITIMER_REAL, &itval, 0) < 0) {
        extern int errno;
        fprintf(stderr, "nspr: can't set interval timer: %d\n", errno);
	_pr_no_clock = 1;
    }
#endif
}

void PR_InitInterrupts(void)
{
    struct sigaction sa;

    /* Setup death of a child handler */
    sa.sa_handler = (void (*)()) _PR_ChildDeathInterruptHandler;
    sa.sa_flags = SA_RESTART;
    sigfillset(&sa.sa_mask);
    sigaction(SIGCHLD, &sa, 0);

    /* Setup async IO handler */
    sa.sa_handler = (void (*)()) _PR_AsyncIOInterruptHandler;
    sa.sa_flags = SA_RESTART;
    sigfillset(&sa.sa_mask);
#ifdef SCO
    sigaction(SIGPOLL, &sa, 0);
#else
    sigaction(SIGIO, &sa, 0);
#endif
}
#endif /* !HPUX */

/************************************************************************/

#if defined(AIX)
char *strdup(const char *src)
{
    char *p;
    int len;

    if (!src) return 0;
    len = strlen(src) + 1;
    p = (char*) malloc(len);
    if (p) {
	memcpy(p, src, len);
    }
    return p;
}
#endif

/************************************************************************/

/*
** Special hacks for xlib. Xlib/Xt/Xm is not re-entrant nor is it thread
** safe.  Unfortunately, neither is mozilla. To make these programs work
** in a pre-emptive threaded environment, we need to use a lock.
*/
int _pr_x_locked = 0;

void PR_XLock(void)
{
    PR_EnterMonitor(_pr_rusty_lock);
    PR_ASSERT(_pr_x_locked == 0);		/* MUST NOT BE RE-ENTERED! */
    _pr_x_locked = 1;
}

void PR_XWait(int ms)
{
    int64 sleep, c;

    LL_I2L(sleep, ms);
    LL_I2L(c, 1000);
    LL_MUL(sleep, sleep, c);

    PR_ASSERT(_pr_x_locked == 1);		/* MUST NOT BE RE-ENTERED! */
    _pr_x_locked = 0;
    PR_Wait(_pr_rusty_lock, sleep);
    PR_ASSERT(_pr_x_locked == 0);		/* MUST NOT BE RE-ENTERED! */
    _pr_x_locked = 1;
}

void PR_XNotify(void)
{
    PR_ASSERT(_pr_x_locked);
    PR_Notify(_pr_rusty_lock);
}

void PR_XNotifyAll(void)
{
    PR_ASSERT(_pr_x_locked);
    PR_NotifyAll(_pr_rusty_lock);
}

void PR_XUnlock(void)
{
    PR_ASSERT(_pr_x_locked);
    _pr_x_locked = 0;
    PR_ExitMonitor(_pr_rusty_lock);
}

int PR_XIsLocked(void)
{
    return _pr_x_locked;
}

/************************************************************************/

typedef struct _PRPollHook _PRPollHook;
struct _PRPollHook {
    int (*func)(int);
};

static int maxPollFDs;
static _PRPollHook *pollHooks;

void PR_SetPollHook(int fd, int (*func)(int))
{
    _PRPollHook *p, *oldPollHooks;
    int is;

    if (fd < 0) return;

    if (func == 0) {
        /* Removing an entry */
        is = _PR_IntsOff();
        pollHooks[fd].func = 0;
        _PR_IntsOn(is, PR_FALSE);
        return;
    }

    if (fd >= maxPollFDs) {
        p = (_PRPollHook*) malloc(sizeof(_PRPollHook) * (fd + 10));

        is = _PR_IntsOff();
        if (maxPollFDs) {
            memcpy(p, pollHooks, maxPollFDs * sizeof(_PRPollHook));
        }
        memset(p + maxPollFDs, 0, ((fd + 10) - maxPollFDs)*sizeof(_PRPollHook));

        oldPollHooks = pollHooks;
        maxPollFDs = fd + 10;
        pollHooks = p;
        pollHooks[fd].func = func;
        _PR_IntsOn(is, PR_FALSE);
        if (oldPollHooks) free(oldPollHooks);
    } else {
        is = _PR_IntsOff();
        pollHooks[fd].func = func;
        _PR_IntsOn(is, PR_FALSE);
    }
}

#define ZAP_SET(_to, _width)				      \
    NSPR_BEGIN_MACRO					      \
	memset(_to, 0,					      \
	       ((_width + 8*sizeof(int)-1) / (8*sizeof(int))) \
		* sizeof(int)				      \
	       );					      \
    NSPR_END_MACRO

#define COPY_SET(_to, _from, _width)			      \
    NSPR_BEGIN_MACRO					      \
	memcpy(_to, _from,				      \
	       ((_width + 8*sizeof(int)-1) / (8*sizeof(int))) \
		* sizeof(int)				      \
	       );					      \
    NSPR_END_MACRO

void _OS_InitIO(void)
{
    if (!_pr_select_lock) {
	_pr_select_lock = PR_NewNamedMonitor(0, "select-lock");
	_pr_read_lock = PR_NewNamedMonitor(0, "read-lock");
	_pr_write_lock = PR_NewNamedMonitor(0, "write-lock");
	sigemptyset(&timer_set);
	sigaddset(&timer_set, SIGALRM);
    }
}

/*
** Pause for i/o, processing the io queue in ioq. This is called with
** interrupts off.
*/
int _PR_PauseForIO(PRBool poll)
{
    PRCList *wq, *pq, *ioq;
    PRPollQueue *pollq;
    PRPollDesc *polld;
    fd_set rd, wr, ex;
    int fd, rv, nr, nw, ne, wantResched, nfds, valid, notify, didNotify;
    int64 max_timeout, timeout, s, us, c;
    struct timeval tv;
    sigset_t oldset;

    wantResched = 0;

    /* Make, arbitrarily, the maximum timeout be 24 hours */
    LL_I2L(s, 24*60*60L);
    LL_I2L(c, 1000000L);
    LL_MUL(max_timeout, s, c);

    ioq = &_pr_ioq;

    /*
    ** Scan workq and find largest descriptor numbers. Also find the
    ** smallest timeout value.
    */
    nr = nw = ne = -1;
    wq = ioq->next;
    timeout = max_timeout;
    while (wq != ioq) {
        pollq = POLL_QUEUE_PTR(wq);
        pq = pollq->queue.next;
        valid = 0;
        while (pq != &pollq->queue) {
            polld = POLL_DESC_PTR(pq);
            fd = polld->fd;
            if (polld->in_flags & PR_POLL_READ) {
                if (fd > nr) nr = fd;
                valid = 1;
            }
            if (polld->in_flags & PR_POLL_WRITE) {
                if (fd > nw) nw = fd;
                valid = 1;
            }
            if (polld->in_flags & PR_POLL_EXCEPTION) {
                if (fd > ne) ne = fd;
                valid = 1;
            }
            pq = pq->next;
        }
        if (valid) {
            if (LL_CMP(pollq->timeout, <, timeout)) {
                timeout = pollq->timeout;
            }
        }
        wq = wq->next;
    }

    if (poll) {
        tv.tv_sec = 0;
        tv.tv_usec = 0;
    } else {
#ifdef SW_THREADS
        /*
        ** Limit timeout to the smaller of the requested timeout or the
        ** first sleepQ waiters time out
        */
        if (!PR_CLIST_IS_EMPTY(&_pr_sleepq)) {
            PRThread *t = THREAD_PTR(_pr_sleepq.next);
            if (LL_CMP(t->sleep, <, timeout)) {
                timeout = t->sleep;
            }
        }
#endif
        /* Convert timeout value into select timeout value */
        LL_DIV(s, timeout, c);
        LL_MOD(us, timeout, c);
        LL_L2I(tv.tv_sec, s);
        LL_L2I(tv.tv_usec, us);
    }

    nfds = -1;
    if (nr > nfds) nfds = nr;
    if (nw > nfds) nfds = nw;
    if (ne > nfds) nfds = ne;
    if (nfds < 0) {
        if (poll) {
            /* Nothing to do right now */
            goto done;
        }

        /*
        ** Nothing to do. Wait for something to do. This is really
        ** where idle acts like idle.
        **
        ** There is *really* nobody to run. Use select to do a
        ** (hopefully) short timeout. Disable clock interrupts
        ** (so that select will actually timeout).
        **
        ** This path of code is executed to keep the scheduler
        ** from blowing up in the uncommon case that there is
        ** no-one else to run.
        */
        sigprocmask(SIG_BLOCK, &timer_set, &oldset);

		PR_LOG(IO, warn, ("begin pause for %d.%d",
                tv.tv_sec, tv.tv_usec));
        (void) _OS_SELECT(0, 0, 0, 0, &tv);
		PR_LOG(IO, warn, ("end pause"));

        wantResched = _PR_ClockTick();
        sigprocmask(SIG_SETMASK, &oldset, 0);
        goto done;
    }
    nfds++;

    /* Clear out select sets using the maximum descriptor number */
    if (nr >= 0) ZAP_SET(&rd, nfds);
    if (nw >= 0) ZAP_SET(&wr, nfds);
    if (ne >= 0) ZAP_SET(&ex, nfds);

    /* Now set the bits that we will be selecting on */
    wq = ioq->next;
    didNotify = 0;
    while (wq != ioq) {
        int notify = 0;
        pollq = POLL_QUEUE_PTR(wq);
        for (pq = pollq->queue.next; pq != &pollq->queue; pq = pq->next) {
            polld = POLL_DESC_PTR(pq);
            fd = polld->fd;
            if (polld->in_flags & PR_POLL_READ) {
                FD_SET(fd, &rd);
                if ((fd < maxPollFDs) && (pollHooks[fd].func != 0)) {
                    int nb = (*pollHooks[fd].func)(fd);
                    if (nb > 0) {
                        /* Pretend that select found something already */
                        polld->out_flags = PR_POLL_READ;
                        notify = 1;
			continue;
                    }
                }
            }
            if (polld->in_flags & PR_POLL_WRITE) {
                FD_SET(fd, &wr);
            }
            if (polld->in_flags & PR_POLL_EXCEPTION) {
                FD_SET(fd, &ex);
            }
        }
        wq = wq->next;
        if (notify) {
            /* Notify the io-waiter */
            PR_ASSERT(pollq->on_ioq);
            PR_REMOVE_LINK(&pollq->links);
            pollq->on_ioq = 0;
            wantResched |= _PR_CondNotify(pollq->mon, _PR_NOTIFY_STICKY);
            didNotify = 1;
        }
    }
    if (didNotify) {
        return wantResched;
    }

    /*
    ** Disable timers during timed select to work around bug in most
    ** unix kernels: if they SA_RESTART a timer then the select never
    ** times out
    */
    sigprocmask(SIG_BLOCK, &timer_set, &oldset);
    PR_LOG(IO, debug,
	   ("select: nfds=%d rd=%x wr=%x ex=%x tv=%d.%d", nfds,
            (nr >= 0) ? *(int*)&rd : -1,
            (nw >= 0) ? *(int*)&wr : -1,
            (ne >= 0) ? *(int*)&ex : -1,
            tv.tv_sec, tv.tv_usec));
    rv = _OS_SELECT(nfds,
                    (nr >= 0) ? &rd : 0,
                    (nw >= 0) ? &wr : 0,
                    (ne >= 0) ? &ex : 0,
                    &tv);
    PR_LOG(IO, debug,
	   ("select returns %d: rd=%x wr=%x ex=%x", rv,
            (nr >= 0) ? *(int*)&rd : -1,
            (nw >= 0) ? *(int*)&wr : -1,
            (ne >= 0) ? *(int*)&ex : -1));
    /*
    ** Select has returned. We need to tick the clock because we
    ** disabled timers (or we don't have a clock). We don't
    ** deschedule this thread until we have to.
    **
    ** Note that we are careful to call the _PR_ClockTick routine
    ** *before* we re-enable the clock signal handler. This way we
    ** are guarantted to not re-enter the _PR_ClockTick routine which
    ** would be disastrous.
    */
    wantResched = _PR_ClockTick();
    sigprocmask(SIG_SETMASK, &oldset, 0);
    if (rv <= 0) {
        /* Timeout or error */
        if (rv == 0) {
            /* Timeout */
            goto done;
        }
        if (rv == -EINTR) {
            /* We were interrupted by something. Oh well */
            goto done;
        }
        /*
        ** Assume (oh boy) that we got an EBADF and that one of the
        ** descriptors we were handed is bad. The code below
        ** discovers which descriptor it was and marks it bad.
        */
	    PR_LOG(IO, error, ("select returns error %d", -rv));
    }

    /* Notify threads waiting for i/o to complete */
    wq = ioq->next;
    while (wq != ioq) {
        pollq = POLL_QUEUE_PTR(wq);

        /* Scan each pollq on the io queue */
        pq = pollq->queue.next;
        notify = 0;
        while (pq != &pollq->queue) {
            polld = POLL_DESC_PTR(pq);
            fd = polld->fd;
            polld->out_flags = 0;
            if (rv < 0) {
                struct stat sb;
                if (fstat(polld->fd, &sb) < 0) {
                    /* Found a bad descriptor */
                    polld->out_flags |= PR_POLL_ERROR;
			PR_LOG(IO, error,
			       ("fd %d was bad", polld->fd));
                }
            } else {
                if (polld->in_flags & PR_POLL_READ) {
                    if (FD_ISSET(fd, &rd)) {
                        polld->out_flags |= PR_POLL_READ;
                    }
                }
                if (polld->in_flags & PR_POLL_WRITE) {
                    if (FD_ISSET(fd, &wr)) {
                        polld->out_flags |= PR_POLL_WRITE;
                    }
                }
                if (polld->in_flags & PR_POLL_EXCEPTION) {
                    if (FD_ISSET(fd, &ex)) {
                        polld->out_flags |= PR_POLL_EXCEPTION;
                    }
                }
            }
            if (polld->out_flags != 0) {
                notify = 1;
            }
            pq = pq->next;
        }

        wq = wq->next;
        if (notify) {
            /* Notify the io-waiter */
            PR_ASSERT(pollq->on_ioq);
            PR_REMOVE_LINK(&pollq->links);
            pollq->on_ioq = 0;
            wantResched |= _PR_CondNotify(pollq->mon, _PR_NOTIFY_STICKY);
        }
    }
  done:;
    return wantResched;
}

void _PR_Idle(void)
{
    for (;;) {
        int is = _PR_IntsOff();
        /*
        ** When idle is actually running we don't need or want to poll
        ** for i/o
        */
        _pr_pollForIO = PR_FALSE;
        _PR_PauseForIO(PR_FALSE);

        /* Now that idle is done for awhile, enable polling */
        _pr_pollForIO = PR_TRUE;
        _pr_lastTimePolled = PR_Now();

        /* Give anybody else a chance to run */
        _PR_IntsOn(is, PR_TRUE);
    }
}

extern PRMonitor *_PR_CGetMonitor(void *address);

int _PR_IOWait(PRPollQueue *pollq)
{
    PRMonitor *mon;
    int is;

    /*
    ** Get (and enter) cache monitor for pollq; store it where the io
    ** handler can find it to do a sticky notify
    */
    mon = _PR_CGetMonitor(pollq);
    pollq->mon = mon;

    /* Add q to i/o handlers work queue */
    is = _PR_IntsOff();
    PR_APPEND_LINK(&pollq->links, &_pr_ioq);
    pollq->on_ioq = 1;
    _PR_IntsOn(is, PR_FALSE);

    /* Wait for i/o to finish or timeout */
    PR_CWait(pollq, pollq->timeout);
    PR_CExitMonitor(pollq);

    /*
    ** Remove pollq from ioq, now that the i/o is done or timed out. We
    ** only need to remove it from the ioq when the PR_CWait returns
    ** before the idle thread notifies us.
    */
    if (pollq->on_ioq) {
        is = _PR_IntsOff();
	if (pollq->on_ioq) {		/* check again because we are racing */
	    PR_REMOVE_LINK(&pollq->links);
	    pollq->on_ioq = 0;
	}
        _PR_IntsOn(is, PR_FALSE);
    }

    pollq->mon = 0;
    return 0;
}

/* XXX go away? */
int _PR_WaitForFD(PRFileDesc *fd, int rd)
{
    return _PR_WaitForFH(fd->osfd, rd);
}

int _PR_WaitForFH(int fd, int reading)
{
    PRPollQueue q;
    PRPollDesc d;

    q.timeout = LL_MAXINT;
    PR_INIT_CLIST(&q.links);
    PR_INIT_CLIST(&q.queue);
    d.fd = fd;
    d.in_flags = reading ? PR_POLL_READ : PR_POLL_WRITE;
    PR_APPEND_LINK(&d.links, &q.queue);
    _PR_IOWait(&q);
    return (d.out_flags & PR_POLL_ERROR) ? -1 : 0;
}

/*
** Wrap up the select system call so that we can deschedule a thread that
** tries to wait for i/o.
*/
#define MAX_DESC	50
#ifdef AIX
int wrap_select(unsigned long width, void *rl, void *wl, void *el,
		struct timeval *tv)
#elif HPUX
int select(size_t width, int *rl, int *wl, int *el, const struct timeval *tv)
#else
int select(int width, fd_set *rd, fd_set *wr, fd_set *ex, struct timeval *tv)
#endif
{
    int i, nfds;
    PRPollQueue q;
    PRPollDesc desc[MAX_DESC], *pd, *pdbase, *pdend;
    struct timeval poll;
#if defined(AIX) || defined(HPUX)
    fd_set *rd = (fd_set*) rl;
    fd_set *wr = (fd_set*) wl;
    fd_set *ex = (fd_set*) el;
#endif
    fd_set r, w, x;

    if (width < 0) {
	errno = EINVAL;
	return -1;
    }

    /*
    ** Call select right now with a zero timeout to see if i/o is already
    ** ready for the caller. If not, then go through the slower path of
    ** blocking this thread.
    **
    ** NOTE: timeout must be zero unless signals are blocked. See the
    ** commentary in _PR_PauseForIO
    */
    poll.tv_sec = 0;
    poll.tv_usec = 0;
    if (rd) { COPY_SET(&r, rd, width); }
    if (wr) { COPY_SET(&w, wr, width); }
    if (ex) { COPY_SET(&x, ex, width); }
    nfds = _OS_SELECT(width, rd, wr, ex, &poll);
    if (nfds < 0) {
        errno = -nfds;
        return -1;
    }
    if (nfds > 0) {
        return nfds;
    }

    /* Setup for pr_io_wait */
    pd = pdbase = desc;
    pdend = desc + MAX_DESC;
  start_over:
    PR_INIT_CLIST(&q.links);
    PR_INIT_CLIST(&q.queue);
    for (i = 0; i < width; i++) {
	int in_flags = 0;
	if (rd && FD_ISSET(i, &r)) {
            if ((i < maxPollFDs) && (pollHooks[i].func != 0)) {
                int nb = (*pollHooks[i].func)(i);
                if (nb > 0) {
                    /* Pretend that select found something already */
                    ZAP_SET(rd, width);
                    if (wr) ZAP_SET(wr, width);
                    if (ex) ZAP_SET(ex, width);
                    FD_SET(i, rd);
                    return 1;
                }
            }
	    in_flags |= PR_POLL_READ;
	}
	if (wr && FD_ISSET(i, &w)) {
	    in_flags |= PR_POLL_WRITE;
	}
	if (ex && FD_ISSET(i, &x)) {
	    in_flags |= PR_POLL_EXCEPTION;
	}
	if (in_flags) {
	    pd->fd = i;
	    pd->in_flags = in_flags;
#ifdef DEBUG_kipp
	    pd->out_flags = 0x8000;
#else
	    pd->out_flags = 0;
#endif
	    PR_APPEND_LINK(&pd->links, &q.queue);
	    pd++;
	    if (pd == pdend) {
		/*
		** Whoops. Ran out of room. Malloc some space for the
		** poll desc's. Then start over and rebuild the list
		** using the new descriptor memory.
		*/
		PR_ASSERT(pdbase == desc);
		pdbase = (PRPollDesc *) calloc(width+1,sizeof(PRPollDesc));
		if (!pdbase) {
		    /* Losing big time */
		    errno = ENOMEM;
		    return -1;
		}
		pdend = pdbase + width + 1;
		pd = pdbase;
		goto start_over;
	    }
	}
    }
    if (!tv) {
        PR_ASSERT(pd > pdbase);
    }

    /* Compute timeout */
    if (tv) {
	int64 s, us, m;
	int32 is, ius;

	is = tv->tv_sec;
	ius = tv->tv_usec;
	while (ius > 1000000) {
	    is++;
	    ius -= 1000000;
	}
	LL_I2L(s, is);
	LL_I2L(us, ius);
	LL_I2L(m, 1000000);
	LL_MUL(s, s, m);
	LL_ADD(q.timeout, s, us);
    } else {
	q.timeout = LL_MAXINT;
    }

    /* Check for no descriptors case (just doing a timeout) */
    if (width == 0) {
	PR_CEnterMonitor(&q);
	PR_CWait(&q, q.timeout);
	PR_CExitMonitor(&q);
	return 0;
    }

    /* wait for i/o */
    _PR_IOWait(&q);

    /* Compute select results */
    if (rd) ZAP_SET(rd, width);
    if (wr) ZAP_SET(wr, width);
    if (ex) ZAP_SET(ex, width);
    nfds = 0;
    while (--pd >= pdbase) {
	if (pd->out_flags) {
	    if (pd->out_flags & PR_POLL_ERROR) {
		/* Tsk tsk. */
		errno = EBADF;
		PR_LOG(IO, error,
		       ("select returns EBADF for %d", pd->fd));
		nfds = -1;
		break;
	    }
	    if (rd && (pd->out_flags & PR_POLL_READ)) {
		FD_SET(pd->fd, rd);
	    }
	    if (wr && (pd->out_flags & PR_POLL_WRITE)) {
		FD_SET(pd->fd, wr);
	    }
	    if (ex && (pd->out_flags & PR_POLL_EXCEPTION)) {
		FD_SET(pd->fd, ex);
	    }
#ifdef DEBUG_kipp
	    if (pd->out_flags == 0x8000) {
		continue;
	    }
#endif
	    nfds++;
	}
    }
    if (!tv) {
        PR_ASSERT(nfds != 0);
    }
    PR_LOG(IO, debug, ("select returns %d", nfds));
    if (pdbase != desc) {
	free(pdbase);
    }
    return nfds;
}

int PR_Select(int width, fd_set *readfds, fd_set *writefds,
	      fd_set *exceptfds, struct timeval *tv)
{
    /* XXXrobm If OSFD's are distinct from PRFileHandles, we have to convert
       a PRFileHandle select set to an OSFD one. */
    return select(width, readfds, writefds, exceptfds, tv);
}

void PR_NonBlockIO(int desc, int on)
{
}
#endif /* XP_UNIX */

/*
** APIs to return system information...
*/
#if !defined(IRIX) && !defined(SOLARIS) 
long _MD_GetOSName(char *buf, long count)
{
    struct utsname info;
    long len = 0;

    if( uname(&info) != EFAULT ) {
        len = PR_snprintf(buf, count, info.sysname);
    }
    return len;
}

long _MD_GetOSVersion(char *buf, long count)
{
    struct utsname info;
    long len = 0;

    if( uname(&info) != EFAULT ) {
        len = PR_snprintf(buf, count, info.release);
    }
    return len;
}

long _MD_GetArchitecture(char *buf, long count)
{
    struct utsname info;
    long len = 0;

    if( uname(&info) != EFAULT ) {
        len = PR_snprintf(buf, count, info.machine);
    }
    return len;
}
#endif /* !defined(IRIX) && !defined(SOLARIS) */