/*
 * kaffe-nspr.c
 *
 * Copyright (c) 1998 Robert S. Thau.  All rights reserved.
 *
 * See "license.terms" for information on usage and redistribution
 * of this file.
 */

/*
 * This file contains glue code to hook up the various Kaffe system
 * interfaces to NSPR.  
 */

/* Kaffe include files */

#include "config.h"
#include "config-std.h"
#include "config-mem.h"
#include "config-io.h"
#include "config-signal.h"
#include "jtypes.h"
#include "access.h"
#include "object.h"
#include "constants.h"
#include "classMethod.h"
#include "baseClasses.h"
#include "lookup.h"
#include "thread.h"
#include "locks.h"
#include "exception.h"
#include "support.h"
#include "external.h"
#include "errors.h"
#include "gc.h"
#include "md.h"
#include "lerrno.h"
#include "java_lang_Thread.h"
#define NOUNIXPROTOTYPES
#include "jsyscall.h"

/* NSPR include files */

#define PROTYPES_H		/* suppress obsolete NSPR typedefs which
				 * duplicate Kaffe's own
				 */

#include <nspr.h>
#include <private/pprthred.h>

/*****************************************************************
 * General utility support...
 */

/*
 * Fatal error handling.  I suspect there may be a routine like this
 * elsewhere, but something this simple I can just as easily write
 * myself...
 */

static void die (char *msg) {
    write (2, msg, strlen (msg));
    PR_Abort();
}

static void initFiles();
static void initSpinlock();

static void initAll () {
    PR_Init(0, 0, 0);		/* All args unused... */
    initFiles();
    initSpinlock();
}

/*****************************************************************
 * NSPR threads adaptation.  Unfortunately, this is kind of nasty...
 */

/* KK Control flags --- abstraction failure?? */

jbool runFinalizerOnExit = 0;
int flag_preemption;		/* Ignored */

/* Glue structures between Kaffe and NSPR threads, and functions that
 * manage them.  Note that since some of the fields can't be set up until
 * the NSPR thread is running, this includes a bit of trampoline play...
 */

typedef struct thread_glue {
    Hjava_lang_Thread *javaThread;
    PRThread *nsprThread;
    void *stackbase;
    void (*func)();
    int stopping;
    jmp_buf exit_magic;
} thread_glue;

static thread_glue kaffe_dead;	/* ptr here is private data of dead thread */

static void destroy_thread_glue (void *vglue) {
    thread_glue *glue = (thread_glue *)vglue;
    unhand ((glue->javaThread))->PrivateInfo = (void*)&kaffe_dead;
    PR_Free (vglue);

    /* ... and, since Kaffe does joins by a wait on the Thread object,
     * have to notify.
     */

    lockMutex (&glue->javaThread->base);
    broadcastCond (&glue->javaThread->base);
    unlockMutex (&glue->javaThread->base);
}

static void *allocThreadGlue (Hjava_lang_Thread *jt, void (*func)(void *)) {
    thread_glue *glue = (thread_glue *) PR_Malloc (sizeof(struct thread_glue));
    
    glue->func = func;
    glue->javaThread = jt;
    glue->nsprThread = 0;	/* Not running yet... */
    glue->stopping = 0;
    
    unhand (jt)->PrivateInfo = (void *)glue;
    return (void*)glue;
}

static void setThreadGlue (void *vglue, char *stackbase) {
    thread_glue *glue = (thread_glue*) vglue;
    
    glue->nsprThread = PR_GetCurrentThread();
    glue->stackbase = stackbase;

    SetExecutionEnvironment (glue->nsprThread, (void *)glue);
}

/*
 * We use NSPR interrupts in an attempt to partially implement Java
 * interrupts and stops.  So, when a blocking NSPR operation terminates
 * abnormally, we need to be prepared to pick up the pieces...
 */

static PRStatus checkInterrupt (PRStatus stat) {
    if (stat != PR_SUCCESS) {
	thread_glue *glue =
	    (thread_glue*) GetExecutionEnvironment (PR_GetCurrentThread ());
	
	if (glue->stopping) {
	    PR_ClearInterrupt();
	    throwException (ThreadDeath);
	}
    }

    return stat;
}

/* Numerical priority values are wired into the Kaffe Java_lang_thread
 * class, so we have to map them onto the smaller set of distinct
 * priorities provided by NSPR
 */

static PRThreadPriority nsprPriority (int priority) {
    if (priority == java_lang_Thread_MIN_PRIORITY) return PR_PRIORITY_LOW;
    if (priority <= java_lang_Thread_NORM_PRIORITY) return PR_PRIORITY_NORMAL;
    if (priority < java_lang_Thread_MAX_PRIORITY) return PR_PRIORITY_HIGH;
    return PR_PRIORITY_URGENT;
}

/* Routines to create threads, including the messy business necessary to
 * set up the primordial java_lang_Thread
 */

static void startHere (void *vglue) {
    thread_glue *glue = (thread_glue*)vglue;
    char dummy;
    
    setThreadGlue (vglue, &dummy);

    if (!setjmp (glue->exit_magic))
	glue->func (glue->javaThread);

    destroy_thread_glue (vglue);
}

static void nsCreateFirst (Hjava_lang_Thread *jt) {
    char dummy;
    initFiles();		/* Here? */

    setThreadGlue (allocThreadGlue (jt, 0),
		   &dummy + 8192); /* XXXXXXX what is sane stk bound? */
    
    PR_SetThreadGCAble();
    
    /* XXX arrange to run finalizers at exit if that is wanted */
}

static void nsCreate (Hjava_lang_Thread *hjt, void *func) {
    Classjava_lang_Thread *jt = unhand (hjt);	/* XXXXXX */
    PR_CreateThreadGCAble (jt->daemon? PR_SYSTEM_THREAD : PR_USER_THREAD,
			   startHere,
			   allocThreadGlue (hjt, (void (*)())func),
			   nsprPriority (jt->priority),
			   PR_LOCAL_THREAD, /* ??? */
			   PR_UNJOINABLE_THREAD, /* Kaffe does joins itself */
			   threadStackSize);
    
    /* Give the new thread a chance to run (Kaffe GC assumes that its
     * housekeeping threads get a chance to set up before the primordial
     * thread needs their services)
     */
    
    PR_Sleep (PR_INTERVAL_NO_WAIT);
}

/* Process termination, status inquiries, and accounting */

static void nsStop (Hjava_lang_Thread *hjt) {
    thread_glue *glue = (thread_glue*) (unhand (hjt)->PrivateInfo);

    glue->stopping = 1;
    PR_Interrupt (glue->nsprThread);
}

static void nsExit () {
    thread_glue *glue =
	(thread_glue*) GetExecutionEnvironment (PR_GetCurrentThread ());
    
    /* NSPR doesn't provide an explicit thread exit() call --- more
     * attempts to legislate good programming practice, I guess.  So
     * we fake it with a longjmp() that forces a return out of our
     * own trampoline... which doesn't work for the primordial thread.
     * So, for the primordial thread, we just exit and hope for the
     * best.  Probably better to explicitly start up a separate thread
     * and have the true primordial thread just exit, for uniformity...
     * but not yet.
     */
    
    if (glue->func != 0)
	longjmp (glue->exit_magic, 1);
}

static bool nsAlive (Hjava_lang_Thread *hjt) {
    void *tpriv = unhand(hjt)->PrivateInfo;
    return (tpriv != 0 && tpriv != &kaffe_dead);
}

static void nsFinalizeThread (Hjava_lang_Thread *hjt) {
    /* Should aready have wiped out the glue structure when the NSPR
     * thread died... so do nothing.
     */
}

static Hjava_lang_Thread *nsCurrentJavaThread () {
    thread_glue *glue =
	(thread_glue*) GetExecutionEnvironment (PR_GetCurrentThread ());
    
    return glue->javaThread;
}

/* Fun with scheduling */

static void nsSleep (jlong msecs) {
    checkInterrupt (PR_Sleep (PR_MillisecondsToInterval (msecs)));
}

static void nsYield () {
    checkInterrupt (PR_Sleep (PR_INTERVAL_NO_WAIT));
}

static void nsSetPriority (Hjava_lang_Thread *hjt, jint prio) {
    thread_glue *glue = (thread_glue *)(unhand (hjt)->PrivateInfo);

    if (glue != &kaffe_dead && glue != 0) {
	PR_SetThreadPriority (glue->nsprThread, nsprPriority (prio));
    }
}

/* GC interface routines. */

static PRStatus scanPtr (PRThread *t, void **ptr, PRUword count, void *arg) {
    extern void walkConservative (void *, uint32); /* KK Abstraction failure */
    extern void markObject (void *);
    
    if (*ptr == GetExecutionEnvironment (t))
	markObject (((thread_glue *)(*ptr))->javaThread);
    else
	walkConservative ((void *)ptr, count * sizeof (void*));
    
    return PR_SUCCESS;
}

static void nsWalkThreads () {
    PR_ScanStackPointers (scanPtr, NULL);
}

static void nsWalkThread (Hjava_lang_Thread *hjt) {
    /* Hmmm... anything needed here?  Stacks are marked in nsWalkThreads,
     * which also marks the glue structures (pointing to the thread objs),
     * and contents of the thread object itself are marked in the ordinary
     * Java walk.
     */
}

/* Stack frame tomfoolery.  Does this even belong here? */
    
static jint nsFrames (Hjava_lang_Thread *hjt) {
    return 0;			/* Following Godmar... watduzitdo? */
}

static void* nsNextFrame (void* ptr) {
#ifdef TRANSLATOR
    exceptionFrame *this = (exceptionFrame *)ptr;
    exceptionFrame *next = (exceptionFrame *)(this->retbp);
    thread_glue *glue =
	(thread_glue*) GetExecutionEnvironment (PR_GetCurrentThread ());
    
    char dummy;
    char *stacklo = &dummy;
    char *stackhi = glue->stackbase;

    if (stacklo > stackhi) {
	char *temp = stacklo;
	stacklo = stackhi;
	stackhi = temp;
    }

    if (stacklo < (char*)next->retbp && (char*)next->retbp < stackhi)
	return next;
    else
	return NULL;
#else  /* TRANSLATOR */
    vmException *this = (vmException *)ptr;
    vmException *next = (vmException *)(this->prev);

    if (next != NULL && next->meth != (Method*)1)
	return next;
    else
	return NULL;
#endif /* TRANSLATOR */
}

ThreadInterface Kaffe_ThreadInterface = {
    nsCreateFirst,
    nsCreate,
    nsSleep,
    nsYield,
    nsSetPriority,
    nsStop,
    nsExit,
    nsAlive,
    nsFrames,
    nsFinalizeThread,
    nsCurrentJavaThread,
    PR_GetCurrentThread,
    nsWalkThreads,
    nsWalkThread,
    nsNextFrame
};

/*****************************************************************
 * NSPR locks.  Note that nslockinit is the first time we get called,
 * so that's where we do our initialization...
 */

static PRInt32 spinCount = -1;
static PRLock *spinLock = 0;

static void initSpinlock () {
    spinLock = PR_NewLock(); 
}

static void nsspinon (void *dummy) {
    if (spinCount >= 0)
	++spinCount;
    else {
	PR_Lock (spinLock); 
	PR_SuspendAll();
	spinCount = 0;
    }
}

static void nsspinoff (void *dummy) {
    if (--spinCount < 0) {
	PR_ResumeAll();
	PR_Unlock (spinLock); 
    }
}

/* KK abstraction failure */

void Tspinoffall () {
    if (spinCount >= 0) {
	spinCount = 0;
	nsspinoff (0);
    }
}

/* Real locks.  Note that a lot of operations are suppressed if we
 * are spin-locked; we assume that the spin-locking thread will not
 * try to grab some lock while spin-locked and keep it after dropping
 * the spin-lock.  BLARGH.
 */

static void nslockinit (iLock *l) {
    static int inited = 0;

    if (!inited) {
	initAll();
	inited = 1;
    }
    
    l->mux = (void*)PR_NewLock();
    l->cv = (void*)PR_NewCondVar ((PRLock *)l->mux);
}

static void nslock (iLock *l) {

    if (spinCount >= 0) {
        /* Don't try to actually lock it; NSPR (legitimately!) gripes */
	l->holder = (void*)PR_GetCurrentThread();
	return;
    }
    
    /* XXX Must check interrupts... */
    PR_Lock ((PRLock *)l->mux);
    l->holder = (void *)PR_GetCurrentThread();
}

static void nsunlock (iLock *l) {
    l->holder = 0;

    if (spinCount < 0)
	PR_Unlock ((PRLock *)l->mux);
}

static void nswait (iLock *l, jlong howlong) {
    /* Note that there are critical sections here which must
     * be guarded against asynchronous termination of a thread...
     * which regrettably extend *inside* PR_WaitCondVar.  Sigh...
     */
    
    int savedcount = l->count;
    void* savedholder = l->holder;
    PRIntervalTime intvl;
    
    if (howlong == 0)
	intvl = PR_INTERVAL_NO_TIMEOUT;
    else 
	intvl = PR_MillisecondsToInterval (howlong);
    
    l->count = 0;
    l->holder = 0;
    checkInterrupt (PR_WaitCondVar ((PRCondVar *)l->cv, intvl));
    
    l->count = savedcount;
    l->holder = savedholder;
}

static void nssignal (iLock *l) {
    PR_NotifyCondVar ((PRCondVar *)l->cv);
}

static void nsbroadcast (iLock *l) {
    PR_NotifyAllCondVar ((PRCondVar *)l->cv);
}

LockInterface Kaffe_LockInterface = {
    nslockinit,
    nslock,
    nsunlock,
    nswait,
    nssignal,
    nsbroadcast,
    nsspinon,
    nsspinoff
};

/*****************************************************************
 * NSPR I/O adaptation
 */

/* Data structure --- table mapping integral "file descriptors" to
 * the NSPR equivalents, which are pointers to structures.
 */

#define MAXFILES 100

static PRFileDesc *prFiles[MAXFILES];
static PRLock *prFilesLock = NULL;

/* Initialize our connection to NSPR files.
 */

static void initFiles ()
{
    int i;
    
    prFilesLock = PR_NewLock();
    
    prFiles[0] = PR_STDIN;
    prFiles[1] = PR_STDOUT;
    prFiles[2] = PR_STDERR;

    for (i = 3; i < MAXFILES; ++i)
	prFiles[i] = NULL;
}

/* Returns index of an empty slot in the table, which is then reserved
 * for the caller (which must then arrange to free it if the operation
 * fails).  Returns -1 if the table is full.
 */

static int findFileSlot ()
{
    int i;
    static PRFileDesc dummy;
    int retval = -1;		/* Not found */

    PR_Lock (prFilesLock);	/* Should check interrupts? */
    
    for (i = 0; i < MAXFILES; ++i) {
	if (prFiles[i] == NULL) {
	    prFiles[i] = &dummy; /* to avoid race conditions... */
	    retval = i;
	    break;
	}
    }

    PR_Unlock (prFilesLock);

    return retval;
}

/*
 * Note that a slot in the table is free.  NB, this does
 * not need to deal with the lock, as it is atomic (on all
 * even vaguely reasonable hardware), and does not interfere
 * in dangerous ways with finds that may be in progress.
 */

static void freeFileSlot (int fd) {
    prFiles[fd] = NULL;
}

/*
 * "FixFD" function --- integrate an FD into the threaded I/O system.
 * NSPR doesn't let us do this.  It doesn't want us to call exec() either,
 * which means that the only use of this that I can find, in UNIXProcess.c,
 * isn't kosher anyway.  Hmmm... suggest to Tim that this be dispensed with?
 */

static int fixfd (int fd) {
    die ("fixfd not supported.  If you weren't trying exec(), \n"
	 "let me know how this happened.");
}

/*
 * Open function.  Translating traditional UNIX flags into the NSPR
 * equivalents is a real pain... fortunately, the file modes are
 * more in line with tradition.
 */

static int nsopen (const char *path, int flags, int mode) {

    /* Translate flags */
    
    int nsflags = 0;
    int howflg = flags & 3;
    int fd = findFileSlot();
    PRFileDesc *prFile;

    if (howflg == 0) nsflags |= PR_RDONLY;
    if (howflg == 1) nsflags |= PR_WRONLY;
    if (howflg == 2) nsflags |= PR_RDWR;

    if (flags & O_CREAT) nsflags |= PR_CREATE_FILE;
    if (flags & O_APPEND) nsflags |= PR_APPEND;
    if (flags & O_TRUNC) nsflags |= PR_TRUNCATE;
    if (flags & O_SYNC) nsflags |= PR_SYNC;

    /* OK, do it */
    
    if (fd < 0) {
	/* errno = EMFILE; --- or something like; Kaffe sys interface
	 * needs to be better thought out here
	 */
	return -1;
    }

    prFile = PR_Open (path, nsflags, mode);

    if (prFile == NULL) {
	/* Do something with value of PR_GetError */
	freeFileSlot (fd);
	return -1;
    }

    prFiles[fd] = prFile;
    return fd;
}

/* Threaded read.  XXX PR_GetError */

static ssize_t nsread (int fd, void *buf, size_t len) {
    return PR_Read (prFiles[fd], buf, len);
}

/* Threaded write.  XXX PR_GetError */

static ssize_t nswrite (int fd, const void *buf, size_t len) {
    return PR_Write (prFiles[fd], buf, len);
}

/* Threaded lseek.  XXX PRGetError */

static off_t nsseek (int fd, off_t pos, int whence) {
    return PR_Seek (prFiles[fd], pos, (PRSeekWhence) whence);
}

/* Threaded close.  XXX PRGetError */

static int nsclose (int fd) {
    PRFileDesc *prFile = prFiles[fd];
    freeFileSlot (fd);

    return PR_Close (prFile);
}

/* Threaded stat.  Note that not all info is available; we do what
 * we can.
 */

static void nsToStat (PRFileInfo *info, struct stat *stvec) {
    /* Get value for mode, as much as we can.  XXX No permission bits(!);
     * could fill in by calling PR_Access... but for now, we punt.
     */

    int flg = 0777;		/* Assume permissions OK for everything */

    if (info->type == PR_FILE_DIRECTORY) flg |= S_IFDIR;
    else if (info->type == PR_FILE_OTHER) flg |= S_IFCHR; /* a guess */
    else flg |= S_IFREG;	/* ditto */

    /* Fill in the stat structure; some fields are best guess or zeroed. */

    memset ((void*)stvec, sizeof (struct stat), 0);
    
    stvec->st_mode = flg;
    stvec->st_size = info->size;
    stvec->st_blksize = 4096;	/* GUESS; blocks made consistent with that */
    stvec->st_blocks = info->size / 4096 + 1;
    stvec->st_ctime = info->creationTime / 1000; /* millisecs to seconds */
    stvec->st_mtime = info->modifyTime / 1000; /* millisecs to seconds */
    stvec->st_atime = stvec->st_mtime; /* GUESS. */
}

static int nsstat (const char *path, struct stat *stvec) {
    PRFileInfo info;
    int statv = PR_GetFileInfo (path, &info);

    if (statv < 0) return statv;
    
    nsToStat (&info, stvec);
    return 0;
}

int nsfstat (int fd, struct stat *stvec) {
    PRFileInfo info;
    int statv = PR_GetOpenFileInfo (prFiles[fd], &info);

    if (statv < 0) return statv;
    
    nsToStat (&info, stvec);
    return 0;
}

/* Threaded select.  Need to translate to poll, which is kind of painful. */

static int nsselect (int nfds, fd_set *readfds, fd_set *writefds,
		     fd_set *xfds, struct timeval *timeout)
{
    PRPollDesc descs [MAXFILES]; /* XXX */
    int kaffe_fds [MAXFILES];	/* XXX */
    int ndescs = 0;
    int retval;
    int i;
    
    /* Convert struct timeval to a PRIntervalTime */
    
    PRIntervalTime interval;

    if (timeout == 0) {
	/* NULL ptr --- block indefinitely */
	interval = PR_INTERVAL_NO_TIMEOUT;
    }
    else {
	interval = PR_SecondsToInterval (timeout->tv_sec) +
	    PR_MicrosecondsToInterval (timeout->tv_usec);
    }
    
    /* Convert bit masks to poll structures; must also remember which
     * Kaffe fd gave rise to each PRPollDesc, so we know which bits to
     * clear when dealing with the result.
     */
    
    if (nfds > MAXFILES) nfds = MAXFILES;
    
    for (i = 0; i < nfds; ++i) {
	int flg = 0;

	if (FD_ISSET (i, readfds)) flg |= PR_POLL_READ;
	if (FD_ISSET (i, writefds)) flg |= PR_POLL_WRITE;
	if (FD_ISSET (i, xfds)) flg |= PR_POLL_EXCEPT;

	if (flg != 0) {
	    PRPollDesc *desc = &descs [ndescs];
	    desc->fd = prFiles [i];
	    desc->in_flags = flg;

	    kaffe_fds [ndescs++] = i;
	}
    }

    /* Do the damn poll */

    retval = PR_Poll (descs, ndescs, interval);

    if (retval < 0) return retval;

    /* ... and unpack the results */

    for (i = 0; i < ndescs; ++i) {
	int bit = kaffe_fds [i];
	int flg = descs [i].out_flags;

	if (! (flg & PR_POLL_READ)) FD_CLR (bit, readfds);
	if (! (flg & PR_POLL_WRITE)) FD_CLR (bit, writefds);
	if (! (flg & (PR_POLL_EXCEPT | PR_POLL_ERR))) FD_CLR (bit, xfds);
    }

    return retval;
}

/* Translating Unix net addresses to NSPR format (so NSPR can translate
 * them back...)
 */

static int sockaddrToNs (const struct sockaddr *unixaddr, PRNetAddr *nsaddr) {
    struct sockaddr_in *uxaddr = (struct sockaddr_in *)unixaddr;
    
    if (unixaddr->sa_family != AF_INET) return -1;

    nsaddr->inet.family = PR_AF_INET;
    nsaddr->inet.port = uxaddr->sin_port;
    nsaddr->inet.ip = uxaddr->sin_addr.s_addr;
    return 0;
}

/* Translating NSPR net addresses to Unix format (for results of
 * getpeername, etc.)
 */

static int sockaddrFromNs (PRNetAddr *nsaddr, struct sockaddr *unixaddr) {
    struct sockaddr_in *uxaddr = (struct sockaddr_in *)unixaddr;
    
    if (nsaddr->inet.family != PR_AF_INET) return -1;

    unixaddr->sa_family = AF_INET;
    uxaddr->sin_port = nsaddr->inet.port;
    uxaddr->sin_addr.s_addr = nsaddr->inet.ip;
    return 0;
}

/* Dealing with returning net addresses.  Messy, mostly due to Berkleyisms.
 */

static void exportNetAddr (PRNetAddr *nsaddr, struct sockaddr *addr, int *len)
{
    if (addr == NULL || len == NULL) return;
    
    if (*len >= sizeof (struct sockaddr_in)) {
	*len = sizeof (struct sockaddr_in);
	sockaddrFromNs (nsaddr, addr);
    }
    else {
	/* Sigh... will this *ever* happen? */
	struct sockaddr_in foo;
	sockaddrFromNs (nsaddr, (struct sockaddr *)&foo);
	memcpy ((void *)addr, (void *)&foo, *len);
    }
}

/* Socket creation */

static int nssocket (int family, int type, int protocol) {
    int fd = findFileSlot ();
    PRFileDesc *nsfd;

    /* Check args and availability of shadow file slot */
    
    if (fd < 0) {
	/* Set errno to EMFILE? */
	return -1;
    }

    if (family != AF_INET) {
	/* Unix domain sockets not supported here yet.  I don't think
	 * anyone cares...
	 */

	return -1;
    }

    /* actually create the socket */
    
    if (type == SOCK_STREAM) nsfd = PR_NewTCPSocket();
    else if (type == SOCK_DGRAM) nsfd = PR_NewUDPSocket();

    if (nsfd == NULL) {
	/* XXX Errno munging */
	freeFileSlot (fd);
	return -1;
    }

    prFiles[fd] = nsfd;
    return 0;
}

/* Connect to a remote server */

static int nsconnect (int fd, struct sockaddr *addr, size_t dummy) {
    PRNetAddr nsaddr;

    if (sockaddrToNs (addr, &nsaddr) < 0) return -1;
    return PR_Connect (prFiles [fd], &nsaddr, PR_INTERVAL_NO_TIMEOUT);
}

/* Accept a connection */

static int nsaccept (int fd, struct sockaddr *addr, size_t *len) {
    PRNetAddr peernsaddr;
    int accfd = findFileSlot();
    PRFileDesc *accpr = PR_Accept (prFiles [fd], &peernsaddr,
				   PR_INTERVAL_NO_TIMEOUT);

    if (accpr == NULL) {
	freeFileSlot (accfd);
	return -1;
    }

    prFiles [accfd] = accpr;
    exportNetAddr (&peernsaddr, addr, len);
    return accfd;
}

/* Get names for socket connection endpoints */

static int nsgetpeername (int fd, struct sockaddr *addr, int *len) {
    PRNetAddr nsaddr;
    int retval = PR_GetPeerName (prFiles[fd], &nsaddr);

    if (retval < 0) return -1;
    exportNetAddr (&nsaddr, addr, len);
    return 0;
}

static int nsgetsockname (int fd, struct sockaddr *addr, int *len) {
    PRNetAddr nsaddr;
    int retval = PR_GetSockName (prFiles[fd], &nsaddr);

    if (retval < 0) return -1;
    exportNetAddr (&nsaddr, addr, len);
    return 0;
}

/* Packet I/O */

static int nsrecvfrom (int fd, void *buf, size_t len, int flags,
		       struct sockaddr *from, int *fromlen)
{
    PRNetAddr nsaddr;
    int ret;
    
    if (flags != 0) return -1;	/* operation not supported by NSPR! */

    ret = PR_RecvFrom (prFiles[fd], buf, len, 0, &nsaddr,
		       PR_INTERVAL_NO_TIMEOUT);
    if (ret < 0) return ret;

    exportNetAddr (&nsaddr, from, fromlen);
    return ret;
}

static int nssendto (int fd, const void *buf, size_t len,
		     int flg, const struct sockaddr *addr,
		     int addrlen)
{
    PRNetAddr nsaddr;
    
    if (flg != 0) return -1;	/* operation not supported by NSPR! */
    if (sockaddrToNs (addr, &nsaddr) < 0) return -1;
    
    return PR_SendTo (prFiles[fd], buf, len, 0, &nsaddr,
		      PR_INTERVAL_NO_TIMEOUT);
}

/* Socket options.   As near as I can tell, these are all described
 * as "obsolete" in the NSPR layer.  So...
 */

static int nssetsockopt (int fd, int level, int optname,
			 const void *optval, int optlen)
{
    /* Not done yet.  Messy */
    return -1;
}

static int nsgetsockopt (int fd, int level, int optname,
			 void *optval, int *optlen)
{
    /* Not done yet.  Messy */
    return -1;
}

static int nswaitpid (pid_t pid, int *status, int options) {
    /* XXX Not supported in this form by NSPR; look into CreateProcess later.
     */
    return -1;
}

/* SystemCallInterface for NSPR.  Functions listed as "unimplemented"
 * are dummies that always return -1; others may have limitations as
 * listed (of which only the *sockopt limitations seem to actually
 * matter.
 */

SystemCallInterface Kaffe_SystemCallInterface = {
    
    fixfd,			/* Unimplemented */
    nsopen,
    nsread,
    nswrite,
    nsseek,
    nsclose,
    nsstat,			/* Partial --- no access checks; other
				 * fields not filled in (e.g., dev, ino)
				 */
    
    PR_MkDir,
    PR_RmDir,
    PR_Rename,
    PR_Delete,
    
    nssocket,			/* AF_INET only */
    nsconnect,			/* AF_INET only */
    nsaccept,
    nsread,
    nsrecvfrom,			/* Partial --- no flags */
    nswrite,
    nssendto,			/* Partial --- no flags */

    nssetsockopt,		/* Unimplemented */
    nsgetsockopt,		/* Unimplemented*/
    nsgetsockname,
    nsgetpeername,

    nsselect,
    nswaitpid			/* Unimplemented */
};

#ifdef NOTDEF
/* Can just put PR_MkDir, PR_RmDir, PR_Rename, PR_Delete in the table?
 * Wrote the following before I figured that out; left in for now.
 */

static int nsmkdir (const char *name, int mode) {
    return PR_MkDir (name, mode);
}

static int nsrmdir (const char *name) {
    return PR_RmDir (name);
}

static int nsrename (const char *old, const char *new) {
    return PR_Rename (old, new);
}

#endif

