/* 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 */
syntax highlighted by Code2HTML, v. 0.9.1