/*  io.c -- input and output routines
 *
 *  Warning:  These routines depend to some extent on the internals of the
 *  standard I/O library.  They have, however, proven surprisingly portable.
 *  The code could be even more portable if we didn't want to make MPD files
 *  compatible with fprintf etc. for use by externals.
 */

#include <ctype.h>
#include <stdarg.h>
#include "rts.h"

#define NOTHING			/* for use as null macro arg under ANSI C */

int mpd_inchar ();
static int rdbool (), rdline (), rdtok (), get ();
static int outchar (), check_error ();
static void flushout (), wready ();

#ifdef MULTI_MPD
static int fdnum ();
#endif


static File fp_table [LAST_SHARED_FD + 1];



/*
 *  Initialize I/O.
 */
void
mpd_init_io ()
{
    int i;

    for (i = 0; i <= LAST_SHARED_FD; i++)
	fp_table[i] = NULL;

    if (SHARED_FD (0)) {
	fp_table[0] = stdin;
	mpd_fd_lock[0].name = "stdin_mutex";
    }
    if (SHARED_FD (1)) {
	fp_table[1] = stdout;
	mpd_fd_lock[1].name = "stdout_mutex";
    }
    if (SHARED_FD (2)) {
	fp_table[2] = stderr;
	mpd_fd_lock[2].name = "stderr_mutex";
    }
}



/*
 *  Open a file.
 *  Return descriptor, or a descriptor for the null file if open fails.
 */
File
mpd_open (fname, mode)
String *fname;
int mode;
{
    File fp;
    int fd;
    static char *flags [] = { "r", "w", "r+" };

    mpd_check_stk (CUR_STACK);
    BEGIN_IO (NULL);

    * (DATA (fname) + fname->length) = '\0';
    if ((fp = fopen (DATA (fname), flags [mode])) == NULL) {
	fp = (File) NULL_FILE;
    } else {
	fd = fileno (fp);
	if (SHARED_FD (fd))		/* update table if file is shared */
	    fp_table[fd] = fp;
#ifdef SHARED_FILE_OBJS
	else {
	    /* can't handle this many open files */
	    fclose (fp);
	    fp = (File) NULL_FILE;	/* indicate failure on return */
	}
#endif
    }

    END_IO (NULL);
    return fp;
}



/*
 *  Move the file pointer.
 */
int
mpd_seek (locn, fp, seektype, offset)
char *locn;
File fp;
int seektype;
int offset;
{
    int posn;

    mpd_check_stk (CUR_STACK);
    BEGIN_IO (fp);
    CHECK_FILE (locn, fp, 0);
    if (fseek (fp, (long) offset, seektype) != 0)
	check_error (locn, fp);
    posn = ftell (fp);
    END_IO (fp);
    return posn;
}



/*
 *  Determine the position of the file pointer.
 */
int
mpd_where (locn, fp)
char *locn;
File fp;
{
    long posn;

    mpd_check_stk (CUR_STACK);
    BEGIN_IO (fp);
    CHECK_FILE (locn, fp, 0);
    if ((posn = ftell (fp)) < 0L)
	check_error (locn, fp);
    END_IO (fp);
    return posn;
}



/*
 *  Flush a file's buffers.  The file remains open.
 */
void
mpd_flush (locn, fp)
char *locn;
File fp;
{
    mpd_check_stk (CUR_STACK);
    BEGIN_IO (fp);
    CHECK_FILE (locn, fp, NOTHING);
    flushout (locn, fp);
    END_IO (fp);
}



/*
 *  Close a file.
 */
void
mpd_close (locn, fp)
char *locn;
File fp;
{
    mpd_check_stk (CUR_STACK);
    BEGIN_IO (fp);
    CHECK_FILE (locn, fp, NOTHING);

#ifdef __linux__
    if ((fp->_flags & (_S_NO_READS & _S_NO_WRITES)) == 0)
	flushout (locn, fp);
#else
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined (__OpenBSD__)
    if (fp->_flags & __SWR)
	flushout (locn, fp);
#else
    if (fp->_flag & _IOWRT)
	flushout (locn, fp);
#endif
#endif

    if (fclose (fp) == EOF)
	check_error (locn, fp);

    /*  We must call END_IO before forgetting the fileno association, since
     *  this would goof up END_IO and cause it to not unlock a fd lock.
     */
    END_IO (fp);

#ifdef MULTI_MPD
    if (SHARED_FD (fdnum (fp)))
	fp_table [fdnum (fp)] = 0;		/* forget fileno association */
#endif
}



/*
 *  Remove a file by name.
 */
Bool
mpd_remove (fname)
String *fname;
{
    Bool rtn;

    mpd_check_stk (CUR_STACK);
    * (DATA (fname) + fname->length) = '\0';
    BEGIN_IO (NULL);
    rtn = (unlink (DATA (fname)) >= 0);
    END_IO (NULL);
    return rtn;
}



/*
 *  mpd_read (locn, fp, argtypes, &arg1, ...) -- read zero or more variables.
 */
int
mpd_read (char *locn, File fp, char *argt, ...)
{
    va_list ap;
    Array *a;
    String *s;
    Real *rp;
    Ptr *pp;
    Bool *bp;
    int *ip;
    long p;

    char x, c, t, buf[100], *cp;
    int nread, len, i, n, ret;
    double d;

    mpd_check_stk (CUR_STACK);
    BEGIN_IO (fp);
    CHECK_FILE (locn, fp, EOF);

    va_start (ap, argt);
    nread = ret = 0;
    while (t = *argt++)  {
	switch (t) {
	    case 'b':	/* boolean argument */
		bp = va_arg (ap, Bool *);
		ret = rdbool (locn, fp, bp);
		break;
	    case 'c':
		cp = va_arg (ap, char *);
		if (rdtok (locn, fp, buf, sizeof (buf) - 1) < 0)
		    ret = EOF;
		else {
		    ret = 0;
		    *cp = buf[0];
		}
		break;
	    case 'i':	/* integer argument */
	    case 'e':	/* enum argument */
		ip = va_arg (ap, int *);
		n = rdtok (locn, fp, buf, sizeof (buf) - 1);
		if (n < 0)
		    ret = EOF;
		else {
		    c = buf[n] = '\0';
		    ret = !mpd_cvint (buf, &i);
		    if (ret == 0)
			*ip = i;	/* store only if valid */
		}
		break;
	    case 'r':
		rp = va_arg (ap, Real *);
		n = rdtok (locn, fp, buf, sizeof (buf) - 1);
		if (n < 0)
		    ret = EOF;
		else {
		    c = buf[n] = '\0';
		    n = sscanf (buf, "%lf%c", &d, &c);
		    ret = (n != 1) || (c != '\0');
		    if (ret == 0)
			*rp = d;	/* store only if valid */
		}
		break;
	    case 'p':
		pp = va_arg (ap, Ptr *);
		n = rdtok (locn, fp, buf, sizeof (buf) - 1);
		if (n < 0)
		    ret = EOF;
		else {
		    c = buf[n] = '\0';
		    if (strcmp (buf, "==null==") == 0) {
			*pp = NULL;
			ret = 0;
		    } else {
			n = sscanf (buf, "%lx%c%c", &p, &x, &c);
			ret = ! (n == 1 || (n == 2 && (x == 'x' || x == 'X')));
			if (ret == 0)
			    *pp = (Ptr) p;	/* store only if valid */
		    }
		}
		break;
	    case 's':	/* string argument */
		s = (String *) va_arg (ap, String *);
		n = rdline (locn, fp, DATA (s), MAXLENGTH (s));
		if (n < 0)
		    ret = EOF;
		else
		    s->length = n;
		break;
	    case 'a':	/* char array argument */
		a = (Array *) va_arg (ap, Array *);
		len = UB (a, 0) - LB (a, 0) + 1;
		n = rdline (locn, fp, ADATA (a), len);
		if (n < 0)
		    ret = EOF;
		else
		    while (n < len)
			ADATA (a) [n++] = ' ';
		break;
	    default:
		ABORT_IO (fp, locn, "bad read format");
	}

	if (ret != 0)  {
	    if (nread > 0)
		IO_RETURN (fp, nread);
	    else if (ret == EOF)
		IO_RETURN (fp, EOF);
	    else
		IO_RETURN (fp, 0);
	}
	nread++;
    }
    va_end (ap);
    IO_RETURN (fp, nread);
}


/*
 *  Read a Boolean literal into given address from the named file.
 *  The accepted values are "true" and "false".
 *  Return 1 if bad, 0 if okay, EOF for EOF.
 */
static int
rdbool (locn, fp, pbool)
char *locn;
File fp;
Bool *pbool;
{
    int n;
    char buf[20];

    n = rdtok (locn, fp, buf, 6);
    if (n == EOF)
	return EOF;
    buf[n] = '\0';
    return !mpd_cvbool (buf, pbool);
}



/*
 *  Read a line to buffer s of size n; return number of chars stuffed, or EOF.
 */
static int
rdline (locn, fp, s, n)
char *locn;
File fp;
char *s;
int n;
{
    register int c, i;

    i = 0;
    for (;;) {
	c = INCH (locn, fp);

	if (c == EOF)
	    return (i != 0) ? i : EOF;
	if (c == '\n')
	    return i;
	if (++i > n)  {
	    ungetc (c, fp);
	    return n;
	}
	*s++ = c;
    }
}



/*
 *  Read a token to buffer s of size n; return number of chars stuffed, or EOF.
 */
static int
rdtok (locn, fp, s, n)
char *locn;
File fp;
char *s;
int n;
{
    int c, i;

    while (isspace (c = INCH (locn, fp)))
	;
    if (c == EOF)
	return EOF;
    i = 0;
    while ((c != EOF) && (!isspace (c)))  {
	if (i < n)
	    s[i++] = c;
	c = INCH (locn, fp);
    }
    while (c != '\n' && isspace (c))
	c = INCH (locn, fp);
    if (c != '\n')
	ungetc (c, fp);
    return i;
}



/*
 *  mpd_printf (locn, fp, sp, format, argtypes, arg1...)
 *  Write zero or more arguments to string or file according to given format.
 *  Either fp (file pointer) or sp (string pointer) is null.
 *
 *  Each conversion is limited to 509 characters as in ANSI C.
 *
 *  Besides the externally documented formats, %r (raw) is provided for use by
 *  the write (), writes (), and put () builtins.  The 509 character limit does
 *  not apply;  NUL characters are ignored;  any precision etc. in the spec
 *  are also ignored.
 */
void
mpd_printf (char *locn, File fp, String *sp, String *str, Ptr argt, ...)
{
    va_list ap;
    char *fmt, a, c, *p, *b, *s;
    double v;
    char xbuf[20], fbuf[512], dbuf[512], obuf[512];
    int n;
    int ssize;
    Array *arr;

    mpd_check_stk (CUR_STACK);
    va_start (ap, argt);

    if (sp != NULL) {
	*DATA (sp) = '\0';
	sp->length = 0;
	ssize = MAXLENGTH (sp);
	PRIV (io_handoff) = 0;
    } else {
	BEGIN_IO (fp);
	CHECK_FILE (locn, fp, NOTHING);
    }

    * (DATA (str) + str->length) = '\0';
    fmt = DATA (str);


    while ((c = *fmt++) != '\0') {
	if (c == '%') {
	    b = fbuf;
	    *b++ = '%';
	    while ((c = *fmt++) != '\0' && !isalpha (c) && c != '%')
		*b++ = c;
	    if (!c)
		ABORT_IO (fp, locn,
		    "unterminated conversion spec (%...) in format");
	    b[0] = c;				/* leave b on conversion char */
	    b[1] = '\0';			/* add terminating NUL */
	    if (c == '%') {
		if (sp != NULL) {
		    if (ssize > sp->length)
			* (DATA (sp) + sp->length++) = '%';
		    else
			ABORT_IO (fp, locn, "result string too small");
		}
		else
		    OUCH (locn, fp, '%');		/* spec was `%...%' */
		continue;
	    }

	    a = *argt++;
	    if (!a)
		ABORT_IO (fp, locn, "insufficient argument list for format");

	    switch (c) {
		case 'q':
		case 'i':
		    if (c == 'q')
			*b = 'o';		/* turn %q into %o */
		    else /* c == 'i' */
			*b = 'd';		/* turn %i into %d */
		    /*FALLTHROUGH*/
		case 'd':
		case 'o':
		case 'x':
		case 'X':
		    if (a != 'i' && a != 'e')
			ABORT_IO (fp, locn, "format calls for an int argument");
		    sprintf (obuf, fbuf, va_arg (ap, int));
		    break;
		case 'b':
		    if (a != 'b')
			ABORT_IO (fp, locn, "format calls for a bool argument");
		    *b = 's';
		    sprintf (obuf, fbuf, va_arg (ap, int) ? "true" : "false");
		    break;
		case 'B':
		    if (a != 'b')
			ABORT_IO (fp, locn, "format calls for a bool argument");
		    *b = 's';
		    sprintf (obuf, fbuf, va_arg (ap, int) ? "TRUE" : "FALSE");
		    break;
		case 'c':
		    if (a != 'c')
			ABORT_IO (fp, locn, "format calls for a char argument");
		    c = va_arg (ap, int);
		    if (sp == NULL && b == fbuf + 1) {	/* if pure "%c" */
			/*
			 * special short circuit path to allow write ('\0')
			 */
			OUCH (locn, fp, c);			
			continue;
		    }
		    sprintf (obuf, fbuf, c);
		    break;
		case 'r':
		    if (a == 's') {
			str = va_arg (ap, String *);
			s = DATA (str);
			n = str->length;
		    } else if (a == 'a') {
			arr = va_arg (ap, Array *);
			s = ADATA (arr);
			n = UB (arr, 0) - LB (arr, 0) + 1;
		    } else {
			ABORT_IO (fp, locn, "string argument expected");
		    }
		    if (n < 0)
			ABORT_IO (fp, locn, "illegal length for write");
		    if (sp != NULL) {
			if (ssize - sp->length >= n) {
			    p = DATA (sp) + sp->length;
			    sp->length += n;
			    while (n--)
				*p++ = *s++;
			}
			else
			    ABORT_IO (fp, locn, "result string too small");
		    }
		    else
			while (n--)
			    OUCH (locn, fp, *s++);
		    continue;
		case 's':
		    if (a == 's') {
			str = va_arg (ap, String *);
			s = DATA (str);
			n = str->length;
		    } else if (a == 'a') {
			arr = va_arg (ap, Array *);
			n = UB (arr, 0) - LB (arr, 0) + 1;
			memcpy (s = dbuf, ADATA (arr), n);
		    } else {
			ABORT_IO(fp,locn,"format calls for a string argument");
		    }
		    if (n < 0 || n > 509)
			ABORT_IO (fp, locn, "illegal string length for %s");
		    s[n] = '\0';
		    sprintf (obuf, fbuf, s);
		    break;
		case 'f':
		case 'e':
		case 'E':
		case 'g':
		case 'G':
		    if (a == 'r')
			v = va_arg (ap, double);
		    else if (a == 'i')
			v = va_arg (ap, int);
		    else
			ABORT_IO (fp, locn,"format calls for a real argument");
		    sprintf (obuf, fbuf, v);
		    break;
		case 'p':
		    if (a != 'p')
			ABORT_IO(fp,locn,"format calls for a pointer argument");
		    p = va_arg (ap, Ptr);
		    if (!p)
			strcpy (xbuf, "==null==");
		    else
			sprintf (xbuf, "%08lX", (long) p);
		    *b = 's';
		    sprintf (obuf, fbuf, xbuf);
		    break;
		case 'h':
		case 'l':
		case 'L':
		    ABORT_IO (fp, locn,
			"illegal size modifier (h/l/L) in format spec");
		default:
		    ABORT_IO (fp, locn,
			"illegal specification character in format");
	    }
	    s = obuf;
	    if (sp != NULL) {
		while ((c = *s++) != '\0') {
		    if (ssize > sp->length)
			* (DATA (sp) + sp->length++) = c;
		    else
			ABORT_IO (fp, locn, "result string too small");
		}
	    }
	    else
		while ((c = *s++) != '\0')	/* now write buffer to file */
		    OUCH (locn, fp, c);
	} else
	    if (sp != NULL) {
		if (ssize > sp->length)
		    * (DATA (sp) + sp->length++) = c;
		else
		    ABORT_IO (fp, locn, "result string too small");
	    }
	    else
		OUCH (locn, fp, c);
    }
    va_end (ap);
    if (*argt)
	ABORT_IO (fp, locn, "too many arguments for format");

    if (sp == NULL) {
	flushout (locn, fp);			/* flush after every write */
	END_IO (fp);
    }
}



/*
 *  Read string value for "get(s)".
 */
int
mpd_get_string (locn, fp, s)
char *locn;
File fp;
String *s;
{
    int n;
    n = get (locn, fp, DATA (s), MAXLENGTH (s));
    if (n != EOF)
	s->length  = n;
    return n;
}



/*
 *  Read character array for "get(a)".
 */
int
mpd_get_carray (locn, fp, a)
char *locn;
File fp;
Array *a;
{
    return get (locn, fp, ADATA (a), UB (a, 0) - LB (a, 0) + 1);
}



/*
 *  Read up to "len" characters from the specified file.
 */
static int
get (locn, fp, str, len)
char *locn;
File fp;
char *str;
int len;
{
    int c;
    int count = 0;

    mpd_check_stk (CUR_STACK);

    BEGIN_IO (fp);
    CHECK_FILE (locn, fp, EOF);
    for (; len > 0; len--, count++) {
	if ((c = INCH (locn, fp)) == EOF) {
	    if (count == 0)
		IO_RETURN (fp, EOF);
	    break;
	}
	*str++ = c;
    }
    IO_RETURN (fp, count);
}



/*
 *  Input a character without blocking other processes.
 *  (Called by INCH macro when the buffer is empty.)
 */
int
mpd_inchar (locn, fp)
char *locn;
File fp;
{
    int c, rtn;

#ifndef __linux__
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined (__OpenBSD__)
    fp->_r++;			/* reset count clobbered by INCH macro */
#else
    fp->_cnt++;			/* reset count clobbered by INCH macro */
#endif
#endif

    wready (fp, INPUT);		/* wait for input file ready */
    c = getc (fp);		/* now that we know it won't wait... */
    if (c < 0)
	rtn = check_error (locn, fp);
    else
	rtn =  c;
    return rtn;
}



/*
 *  Output a character without blocking other processes.
 *  (Called by OUCH macro when the count is zero.)
 *
 *  For some buffering schemes, the count is always zero, so check the buffer
 *  contents;  if there's already something there, we checked before and
 *  nothing's been written since, so we need not check again.
 *
 *  NOT FOOLPROOF; depends on how stdio works.
 */
static int
outchar (locn, fp, c)
char *locn;
File fp;
char c;
{

#ifdef __linux__
    if (fp->_pptr == fp->_pbase)	/* only need to select () once */
	wready (fp, OUTPUT);		/* wait for output file ready */
#else
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined (__OpenBSD__)
    fp->_w++;				/* reset count clobbered by OUCH macro*/
    if (fp->_p == fp->_bf._base)	/* only need to select () once */
	wready (fp, OUTPUT);		/* wait for output file ready */
#else
    fp->_cnt++;				/* reset count clobbered by OUCH macro*/
    if (fp->_ptr == fp->_base)		/* only need to select () once */
	wready (fp, OUTPUT);		/* wait for output file ready */
#endif
#endif

    if (putc (c, fp) == EOF)		/* now we know this is safe */
	check_error (locn, fp);		/* handle error */
    return c;
}



/*
 *  Flush an output file without blocking other processes.
 *  Assumes any necessary locks are already held.
 *
 *  NOT FOOLPROOF: has been seen to fail on Vax, work on Sun.
 */
static void
flushout (locn, fp)
char *locn;
File fp;
{

#ifdef __linux__
    if (fp->_pptr == fp->_pbase)	/* if buffer empty, return */
	return;
#else
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined (__OpenBSD__)
    if (fp->_p == fp->_bf._base)	/* if buffer empty, return */
	return;
#else
    if (fp->_ptr == fp->_base)		/* if buffer empty, return */
	return;
#endif
#endif

    wready (fp, OUTPUT);		/* wait until ready for output */
    if (fflush (fp) == EOF)		/* flush buffer */
	check_error (locn, fp);		/* handle error */
}



/*
 *  Wait until a file is ready for input or output.
 */
static void
wready (fp, inout)
File fp;
enum io_type inout;
{
    int fd;
    fd_set fdset;
    static fd_set zeroset;

    if (inout == OUTPUT && !mpd_async_flag)
	return;			/* don't wait -- want synchronous output */

    fdset = zeroset;

#ifdef MULTI_MPD
    fd = fdnum (fp);	/*  if fd is not one of our shared descriptors, then
			 *  below we will have the IO Server do the work */
    if (SHARED_FD (fd)) {

	FD_SET (fd, &fdset);
	if (mpd_num_job_servers > 1)
	    UNLOCK (mpd_fd_lock[fd], "wready");	/* begin_io didn't lock if 1 */

	mpd_iowait (&fdset, &fdset, inout);

	if (mpd_num_job_servers > 1)
	    LOCK (mpd_fd_lock[fd], "wready");
    } else {
#else
	{
#endif /* MULTI_MPD */

	/* this must really be run on the IO Server -- the fileno stuff */
	BEGIN_IO (NULL);
	fd = fileno (fp);
	fdset = zeroset;
	FD_SET (fd, &fdset);
	END_IO (NULL);
	mpd_iowait (&fdset, &fdset, inout);
    }
}



/*
 *  Check for I/O errors and abort if there were any.
 *  If not, clear possible EOF condition.  Return EOF if so, or 0.
 *  Note: this is always called inside a BEGIN_IO call, so we don't
 *  need to do it.
 */
static int
check_error (locn, fp)
char *locn;
File fp;
{
    int rtn = 0;

    if (ferror (fp))  {
	perror ("MPD I/O");
	ABORT_IO (fp, locn, "I/O error");
    }
    if (feof (fp))  {
	clearerr (fp);		/* clear EOF */
	rtn = EOF;
    }
    return rtn;
}



/*  The rest of this file contains routines used only in MultiMPD.  */

#ifdef MULTI_MPD



/*
 *  mpd_begin_io and mpd_end_io are targets of the macros BEGIN_IO and END_IO if
 *  files and file buffers are not sharable among processes.  Begin and end are
 *  not quite symmetric: begin could be called as the IO server from scheduler,
 *  if there were no waiting IO jobs, so in this case we should just return.
 *  However, end must be called from the IO server.
 */
int
mpd_begin_io (fp)
File fp;
{
    int fd;

    if (mpd_num_job_servers == 1)
	return 0;			/* nothing to do */

    /*  We don't need to do anything with a null or noop file.  A
     *  null file will be handled by our caller, so in both cases
     *  we can return here */
    if (fp == (File) NOOP_FILE || fp == (File) NULL_FILE)
	return 0;

    /*  if it is a shared file object, then the IO Server and other
     *  job servers must get the lock.  If it is not, then other job
     *  servers must hand it off to the IO server.  When the IO server
     *  gets it it doesn't have to grab that lock */

    fd = fdnum (fp);
    if (SHARED_FD (fd)) {
	/* we can go ahead and grab the lock (if not already held) */
	/*  note that we do not set the status to DOING_IO if we don't
	 * ``hand off'' this proc to the IO SERVER (see mpd_end_io) */
	LOCK (mpd_fd_lock[fd], "mpd_begin_io");
	return 0;
    } else if (I_AM_IOSERVER) 
	return 0;			/* nothing to do */
#ifdef SHARED_FILE_OBJS
    else if (fp == 0)
	return 0;			/* nothing to do */
#endif

    LOCK_QUEUE ("mpd_begin_io");	/* will be released by scheduler */
    if (CUR_PROC->status == BLOCKED)
	mpd_malf ("mpd_begin_io called with BLOCKED cur_proc");
    CUR_PROC->status = DOING_IO; /* for mpd_scheduler: doesn't resched */
    mpd_enqueue (&mpd_io_list, CUR_PROC);
    mpd_scheduler ();
    return 0;
}



int
mpd_end_io (fp)
File fp;
{
    int fd;

    if (mpd_num_job_servers == 1)
	return 0;			/* nothing to do */


    /*  We don't need to do anything with a null or noop file.  A
     *  null file will be handled by our caller, so in both cases
     *  we can return here */
    if (fp == (File) NOOP_FILE || fp == (File) NULL_FILE)
	return 0;

    fd = fdnum (fp);

    if (CUR_PROC->status != DOING_IO) {
	/* mpd_begin_io did not hand this off between job servers, so
	 * we won't either. */
	if (SHARED_FD (fd))
	    UNLOCK (mpd_fd_lock[fd], "mpd_end_io");
	return 0;			/* nothing to do ! */
    }

    LOCK_QUEUE ("mpd_end_io");		/* will be released by scheduler */
    mpd_reschedule (CUR_PROC);
    mpd_scheduler ();
    return 0;
}



/*
 *  Look up the file descriptor for fp.
 *
 *  We only track files that different job servers are allowed to share.
 *  If fp points to a non-sharable file object, a negative number is returned
 *  (in which case the caller will have to have the IO activity done by
 *  the IO server).
 *
 *  If file objects are not not shared in general, then FIRST_SHARED_FD
 *  and LAST_SHARED_FD can still give a range of files that *are* shared.
 *
 *  We assume atomic access to each entry, so there is no locking of the table.
 */

static int
fdnum (fp)
File fp;
{
    int i;

    if (fp != NULL)
	for (i = FIRST_SHARED_FD; i <= LAST_SHARED_FD; i++)
	    if (fp_table[i] == fp)
		return i;
    return -1;
}
#endif /* MULTI_MPD */
