Google
 

Trailing-Edge - PDP-10 Archives - SRI_NIC_PERM_FS_1_19910112 - c/lib/usys/open.c
There are 10 other files named open.c in the archive. Click here to see a list.
/*
**	OPEN - URT lowest-level file-opening
**
**	(c) Copyright Ken Harrenstien 1989
**		for all changes after v.226, 11-Aug-1988
**	Copyright (C) 1986 by Ian Macky, SRI International
**	Edits for ITS:  Copyright (C) 1988 Alan Bawden
**
**	Also provides low-level global routines for other I/O calls.
**	See the UIODAT.C module for definitions of the internal I/O tables
**	and comments on their use.
*/

#include <c-env.h>
#if SYS_T20+SYS_10X+SYS_T10+SYS_CSI+SYS_WTS+SYS_ITS	/* Systems supported */

#include <limits.h>		/* For CHAR_BIT */
#include <errno.h>
#include <stddef.h>		/* for NULL */
#include <string.h>
#include <sys/usysio.h>
#include <sys/usysig.h>
#include <sys/usytty.h>
#include <sys/file.h>

#if SYS_T20+SYS_10X
#include <jsys.h>
#include <strung.h>
#define FILENAME_SIZE (40*6)	/* Max size of a filespec */

#elif SYS_ITS
#include <sysits.h>
#define FILENAME_SIZE 200	/* Max size of a filespec */

#elif SYS_T10+SYS_CSI+SYS_WTS
#include <muuo.h>
#include <uuosym.h>
#include <macsym.h>
#include <ctype.h>		/* For filename parser */
#endif

/* Exported functions */
int creat(), open();		/* System calls */
int _uiofd(), _openuf();	/* Internal stuff */
struct _ufile *_ufcreate();
#if SYS_T20+SYS_10X
int _gtjfn();			/* Return JFN for general pathname */
int _rljfn();
#elif SYS_T10+SYS_CSI+SYS_WTS
int _fparse(), _frename(), _opnrel(), _flookup();
int _t10error();
#endif

/* Imported functions */
extern int _dvtype();		/* from stat() */
extern void _panic();
#if SYS_T20+SYS_10X
extern int _gtfdb();		/* ditto */
#elif SYS_T10+SYS_CSI+SYS_WTS
extern int _blkprime();		/* from write() */
#endif

static int opensys();		/* Basic OS-dependent open routine */

#define WORD_BIT (sizeof(int)*CHAR_BIT)		/* Generality never hurts */
int
creat(path, mode)
char *path;
int mode;
{
    return open(path, (O_WRONLY | O_CREAT | O_TRUNC), mode);
}

int
open(path, flags, mode)
char *path;
int flags, mode;
{
    struct _ufile *uf;
    int fd, err;

    USYS_BEG();
    if ((fd = _uiofd()) == -1 || !(uf = _ufcreate()))
	USYS_RETERR(EMFILE);	/* Failed to get FD slot or UF struct */
    if (err = opensys(uf, path, flags, mode))
	USYS_RETERR(err);	/* Failed somehow */
    _openuf(uf, flags);		/* Set up UF with given flags */
    _uffd[fd] = uf;		/* Set up UF mapping for the FD! */
    USYS_RET(fd);
}
/* _OPENUF - Auxiliary used by _runtm(), open(), pipe() to share common code.
**	uf_ch and uf_flgs must already be set.
**	uf_buf is either NULL or points to an allocated buffer for re-use.
**		(Currently it must always be NULL!)
**	Sets:
**		uf_flgs (OR'd in)
**		uf_type
**		uf_dnum
**		uf_buf
**		uf_cp
**		uf_cnt
**		uf_pos
**		uf_eof
**		uf_nopen
**		uf_zcnt (on ITS)
*/
int
_openuf(uf, flags)
struct _ufile *uf;
int flags;
{
    /* Set various default values if nothing's already there. */
    if (!(uf->uf_flgs&(UIOF_READ|UIOF_WRITE))) {	/* Set R/W flags */
	if (flags & O_RDWR) uf->uf_flgs |= (UIOF_READ | UIOF_WRITE);
	else uf->uf_flgs |= ((flags & O_WRONLY) ? UIOF_WRITE : UIOF_READ);
    }
    if (!uf->uf_bsize) uf->uf_bsize = CHAR_BIT;
    if (!uf->uf_nbpw) uf->uf_nbpw = WORD_BIT / uf->uf_bsize;
    if (uf->uf_type < 0)		/* Get UIO_DVxxx device type */
	uf->uf_type = _dvtype(uf->uf_ch);

    /* See if device can hang waiting for input or not.  This should be
    ** system-dependent, actually.
    */
    if (uf->uf_type != UIO_DVDSK)	/* If not disk device, */
	uf->uf_flgs |= UIOF_CANHANG;	/* say input hangage possible */

    if (uf->uf_type == UIO_DVTTY) {	/* If TTY, fix up device number */
	int i;
	if (uf->uf_ch == UIO_CH_CTTRM) /* Always use 0 for control TTY */
	    i = 0;
	else {				/* If a random TTY, pick another */
	    for (i = 1; _ttys[i].tt_uf != NULL;)
		if (++i >= _NTTYS)
		    _panic("open() - opening too many terminal devices");
	}

	/* i now has index into _ttys[] */
	if (_ttys[i].tt_uf == NULL) {
	    _ttys[i] = _deftty;		/* Initialize whole struct! */
	    _ttys[i].tt_uf = uf;	/* Point back to UF */
	}
	uf->uf_dnum = i;		/* Point to tty struct # */
#if SYS_T10
	/* Attempt to get UDX, ignore error for now (what else to do??) */
	if (uf->uf_ch == UIO_CH_CTTRM)
	    MUUO_ACVAL("TRMNO.", -1, &_ttys[i].tt_udx);
	else {
	    int ln = 0;
	    _KCCtype_char6 nam[6];
	    MUUO_ACVAL("DEVNAM", uf->uf_ch, (int *)nam); /* Get TTYnnn */
	    if (nam[3]) {			/* Figure line # from SIXBIT */
		ln = nam[3] - 020;
		if (nam[4]) {
		    ln = (ln<<3) + (nam[4] - 020);
		    if (nam[5]) {
			ln = (ln<<3) + (nam[5] - 020);
		    }
		}
	    }
	    _ttys[i].tt_udx = uuosym(".UXTRM") + ln;
	}
#elif SYS_CSI			/* CSI did this one right */
	_ttys[i].tt_udx = XWD(-1, uf->uf_ch);
#endif
    }

    /* OK, all ready to go!  Set confirmation that UF is open. */
    uf->uf_nopen = 1;		/* UF is open, with only one FD */
}
/* _UIOFD - Find a spare FD (File Descriptor).
**	Returns -1 if no more FD slots available.
**	Used by open(), pipe(), URT startup.
*/
int
_uiofd()
{
    int fd;

    for (fd = 0; fd < OPEN_MAX; fd++)
	if (!_uffd[fd])
	    return fd;
    errno = EMFILE;
    return -1;
}

/* _UFCREATE - Find a spare UF slot and return pointer to it.
**	May return NULL if there are no free slots left.
**	Used by open(), pipe(), and URT startup.
**
**	On ITS, TOPS-10, and WAITS, the uf_ch value is automatically set
**	to a corresponding channel number from 0-017 inclusive, since there
**	is a one-to-one correspondence between UF number and channel number.
*/
struct _ufile *
_ufcreate()
{
    register int i;
    struct _ufile *uf;

    for (i = 0, uf = _uftab; uf->uf_nopen > 0; ++uf)
	if (++i >= OPEN_UF_MAX) {
	    errno = EMFILE;		/* No free slots, fail */
	    return NULL;
	}
    /* Found a free slot! */
    if (uf->uf_buf)			/* Paranoia check; close() always */
	_panic("_ufcreate: unreleased IOB");	/* frees any buffers. */
    memset((char *)uf, 0, sizeof(*uf));	/* Clear UF struct completely! */
    uf->uf_type = -1;		/* Device type unknown */
    uf->uf_flen = -1;		/* File length unknown */

#if SYS_ITS	/* On ITS, store correct SYSCALL argument for channel */
    uf->uf_ch = SC_IMM(i);
#elif SYS_T10+SYS_CSI+SYS_WTS
    uf->uf_ch = i;		/* Just use plain channel # */
#endif
    return uf;
}

/* _UIOBUF - Set up buffer for a UF
**	uf_bsize and uf_nbpw must already be set.
**	Also used by read().
*/
int
_uiobuf(uf)
register struct _ufile *uf;
{
    if (!uf->uf_buf) {
	if (!(uf->uf_buf = _iobget())) {
	    errno = EMFILE;		/* Too many open files */
	    return 0;
	}
	switch (uf->uf_bsize) {
	case 7:
	    uf->uf_bpbeg = (char*)(int)(_KCCtype_char7*)(uf->uf_buf->b_data);
	    uf->uf_blen = uf->uf_nbpw
			* sizeof(uf->uf_buf->b_data)/sizeof(int); /* # wds */
	    break;
	case 8:
	    uf->uf_bpbeg = (char*)(int)(_KCCtype_char8*)(uf->uf_buf->b_data);
	    uf->uf_blen = uf->uf_nbpw
			* sizeof(uf->uf_buf->b_data)/sizeof(int); /* # wds */
	    break;
	case 9:
	default:
	    uf->uf_bpbeg = uf->uf_buf->b_data;
	    uf->uf_blen = sizeof(uf->uf_buf->b_data);
	    break;
	}
    }
    return 1;
}
#if SYS_T20+SYS_10X	/* TOPS-20/TENEX OS-dependent file opening code */

#define MAX_LINE	(512)		/* size of a GP string buffer */

struct filename {			/* for C:sys/xxx.h */
    char path[FILENAME_SIZE];		/* "sys" */
    char device[FILENAME_SIZE];		/* "C" - could be a logical! */
    char directory[FILENAME_SIZE];	/* NULL */
    char filename[FILENAME_SIZE];	/* "xxx.h" */
};

extern int _gtfdb();		/* used by various routines, defined in stat */

static int get_tricky();	/* tricky logical name expander for get_jfn */
static int get_jfn();		/* work-horse for getting a file */
static int o_gtjfn();		/* lowest level gtjfn, which does the GTJFN% */

static void parse_filename();	/* parse up a TOPS-20 filespec */
static void make_filename();	/* construct a TOPS-20 filespec from pieces */
static int log_expand();	/* expand a logical device */
static int o_openf();		/* Call OPENF% */

/* OPENSYS for TOPS-20/TENEX
*/
static int
opensys(uf, path, flags, mode)
struct _ufile *uf;
char *path;
int flags, mode;
{
    int jfn;

    if (flags & O_SYSFD) {
	jfn = (int) path;	/* "path" is actually JFN. */
    } else {
	/* This would be the point to translate the protection modes
	 * and pass to the GTJFN% as a ";Pnnnnnn" suffix for use if
	 * creating a new file (or version).
	 *	tnxprot(mode, &protstr);
	 */
	if (!(jfn = _gtjfn(path, flags)))
	    return ENOENT;	/* Failed to get JFN for file */
    }
    uf->uf_ch = jfn;		/* Store the JFN */

/*
 *	check to see if this is an old file, or a new one.  if FB_NXF
 *	is set then the file is new and doesn't exist (until closed)
 */

    if (!(_gtfdb(jfn, _FBCTL) & FB_NXF))
	uf->uf_flgs |= UIOF_OLD;
/*
 *	now open the file
 */
    if (!(o_openf(uf, flags))) {
	if ((flags&O_SYSFD)==0)	/* Failed, release the JFN if we created one */
	    _rljfn(jfn);
	return ENOENT;		/* and lose-return */
    }

    if (flags & O_APPEND) {	/* for append file, read starting position */
	int ablock[5];
	ablock[1] = uf->uf_ch;
	uf->uf_pos = (jsys(RFPTR, ablock)) ? ablock[2] : 0;
    }
    return 0;			/* Won, return without error! */
}
#endif /* SYS_T20+SYS_10X */
#if SYS_T20+SYS_10X
/*
 *	Get a JFN on a pathname.
 *	This is the routine that converts Unix-style pathnames into
 *	T20 filenames.
 */

int _gtjfn(path, flags)
char *path;
int flags;
{
    struct filename file;
    char tried[MAX_LINE];			/* devices already tried */

    parse_filename(path, &file);		/* parse up the filename */
    if (!*file.path)				/* if no unix-style path */
	return get_jfn(path, flags);		/* spec, then this is easy */
    *tried = '\0';				/* nothing tried yet */
    return get_tricky(&file, tried, 1, flags);	/* alas, do tricky things */
}

/*
 *	tricky logical-device follower.  this traces out logical devices
 *	and generates full paths from the different end-directories,
 *	keeping at it until it finds the file.  this routine is not needed
 *	unless foo/ style filespecs are being used.
 *
 *	tried[] is a buf that get_tricky stores devices it has tried into,
 *	so that it won't re-try anything and potentially get into a loop.
 */

/** WARNING!!! THIS CODE CRAPS OUT BADLY IF THE LOGICAL NAME HAS NO
*** EXPLICIT DEVICE SPEC!  FIX LATER!!  --KLH 4/17/89
**/

static int get_tricky(file, tried, job_wide_flag, flags)
struct filename *file;
char *tried;
int job_wide_flag;
{
    char expansion[MAX_LINE], o_device[FILENAME_SIZE], token[FILENAME_SIZE];
    char new_path[MAX_LINE], *cp, *p, *next;
    int jfn;

    if (!log_expand(file->device, expansion, job_wide_flag)) {
	make_filename(new_path, file);		/* not a logical device */
	return get_jfn(new_path, flags);	/* so just look for it. */
    }
    strcpy(o_device, file->device);		/* save original device */
    for (cp = expansion; *cp; cp = ++next) {	/* loop over expansion. */
	next = strchr(cp, ',');			/* find separator */
	if (next) *next = '\0';			/* tie this one off */
	if (p = strchr(cp, '<')) {		/* if got a directory */
	    *strchr(cp, ':') = '\0';		/* kill the colon */
	    strcpy(file->device, cp);		/* device from expansion */
	    *strchr(p, '>') = '\0';		/* kill trailing > */
	    strcpy(file->directory, ++p);	/* directory from expansion */
	    make_filename(new_path, file);	/* make up the new path */
	    if (jfn = get_jfn(new_path, flags))
		return jfn;			/* got it! */
	} else {
	    *strchr(cp, ':') = '\0';		/* kill the colon */
	    job_wide_flag = (strCMP(o_device, cp) != 0); /* same device on */
	    *token = (job_wide_flag) ? '*' : '$'; /* right side means sys */
	    strcpy(token + 1, cp);		/* construct device token */
	    if (!strSTR(tried, token)) {	/* if haven't tried this */
		strcat(tried, token);		/* we're trying it now */
		strcpy(file->device, cp);	/* try this device now */
		if (jfn = get_tricky(file, tried, job_wide_flag, flags))
		    return jfn;
	    }
	}
    }
    return 0;					/* not found... */
}

/*
 *	do the real work of finding a file
 */

static int get_jfn(path, flags)
char *path;
int flags;
{
    int jfn, bits;

    bits = (flags & O_T20_WILD) ? GJ_IFG : 0;
    if (flags & (O_RDWR | O_WRONLY | O_APPEND)) {
/*
 *	Normally we always create a new version for writing.
 *	The O_T20_WROLD flag exists in order to override this and
 *	act like UNIX if such behavior is ever desirable (ugh).
 *	Note that if O_APPEND or O_RDWR is set, we always look
 *	for an existing file first.
 */
	if (!(flags & (O_APPEND | O_RDWR | O_T20_WROLD)))
	    return o_gtjfn(path, GJ_FOU);
	else if (jfn = o_gtjfn(path, GJ_OLD)) {
	    if ((flags & O_CREAT) && (flags & O_EXCL)) {
		_rljfn(jfn);
		errno = EEXIST;
		return 0;
	   }
	} else if (!(flags & O_CREAT) || !(jfn = o_gtjfn(path, GJ_FOU))) {
	    errno = ENOENT;
	    return 0;
	} else return jfn;
    } else return o_gtjfn(path, ((flags & O_T20_WILD) ? GJ_IFG : 0) | GJ_OLD);
}
/*
 *	lowest level actual GTJFN% call
 */

static int o_gtjfn(path, flags)
char *path;
int flags;
{
    int arg_block[5];

    arg_block[1] = GJ_SHT | flags;
    arg_block[2] = (int) (path - 1);
    return (jsys(GTJFN, arg_block)) ? arg_block[1] : 0;
}
/*
 *	take apart a TOPS-20 filespec and put the pieces in the given
 *	(struct filename) structure.
 */

static void parse_filename(path, file)
char *path;
struct filename *file;
{
    char *start, *part;
    int c, length;

    *file->path = *file->device = *file->directory = *file->filename = '\0';

    do {					/* loop over pathname */
	start = path;				/* cp to start of part */
	length = 0;				/* length of part */
	part = NULL;				/* what this part is */
	while (!part) switch (c = *path++) {
	    case '\26':	path++; length++; break;	/* ^V quote */
	    case '\0':	part = file->filename; break;
	    case ':':	part = file->device; break;
	    case '<':	start = path; continue;
	    case '>':	part = file->directory; break;
	    case '/':	part = file->path; break;
	    default:	length++;
	}
	strncpy(part, start, length);
	part[length] = '\0';
    } while (c);
}

/*
 *	given a (struct filename) structure, construct a TOPS-20 filename
 *	from the pieces
 */

static void make_filename(buf, file)
char *buf;
struct filename *file;
{
    *buf = '\0';			/* initialize buffer always */
    if (*file->device) {
	strcat(buf, file->device);
	strcat(buf, ":");
    }
    if (*file->directory || *file->path) {
	strcat(buf, "<");
	if (*file->directory)
	    strcat(buf, file->directory);
	strcat(buf, ".");
	if (*file->path)
	    strcat(buf, file->path);
	strcat(buf, ">");
    }
    if (*file->filename) strcat(buf, file->filename);
}
/*
 *	expand the logical device *from (which should not include the
 *	trailing colon) to *to.  if (job_wide) then a local expansion is
 *	tried first, else just system-wide.
 */

static int log_expand(from, to, job_wide)
char *from, *to;
int job_wide;
{
    int ablock[5];

    ablock[2] = (int) (from - 1);
    ablock[3] = (int) (to - 1);
    if (job_wide) {				/* not system-logical only? */
	ablock[1] = _LNSJB;			/* job-wide */
	if (jsys(LNMST, ablock)) return 1;	/* local expansion worked */
    }
    ablock[1] = _LNSSY;				/* system-wide */
    return jsys(LNMST, ablock);
}
#define old_file(uf)	(uf->uf_flgs & UIOF_OLD)
#define new_file(uf)	(!(uf->uf_flgs & UIOF_OLD))

static int
o_openf(uf, flags)
struct _ufile *uf;
int flags;
{
    int byte_size;
    int open_mode = 0;
    int arg_block[5];

/*
 *	convert file.h flags into OPENF% bit flags
 */
    switch (flags & (O_RDONLY | O_WRONLY | O_RDWR | O_APPEND)) {
	case (O_WRONLY | O_APPEND):
	case O_APPEND:	open_mode = OF_APP; break;
	case O_WRONLY:	open_mode = OF_WR; break;
	case O_RDWR:	open_mode = OF_RD | OF_WR | OF_PLN; break;
	case (O_RDWR | O_APPEND):
			open_mode = OF_RD | OF_WR | OF_APP | OF_PLN; break;
	default:	open_mode = OF_RD | OF_PLN; break;
    }
/*
 *	you can't open an existing file for write-only (not read-write),
 *	and not have it be truncated.  sorry, kids.
 */
    if (old_file(uf) && (open_mode == OF_WR) && !(flags & O_TRUNC)
	&& _dvtype(uf->uf_ch) == _DVDSK) {
	errno = ETRUNC;			/* new T20 error code! */
	return 0;
    }
/*
 *	to avoid truncating an existing file, you need to open it in
 *	r/w mode, even if you just want write.
 */
    if (old_file(uf) && !(flags & (O_TRUNC | O_APPEND)))
	open_mode |= OF_RD;
/*
 *	If a byte size is explicitly requested, use that.
 *	Otherwise:
 *		for a new file, use 9 if O_BINARY, else 7.
 *		for an old file, use the file bytesize.
 *			A size of 0 or 36 is handled as for a new file.
 *			Any other size is simply used - if this is not
 *			one of 7, 8, or 9 then the results are unpredictable.
 */
    switch (flags & O_BSIZE_MASK) {
	case O_BSIZE_7: byte_size = 7; break;
	case O_BSIZE_8: byte_size = 8; break;
	case O_BSIZE_9: byte_size = 9; break;
	default:	/* Error, but nothing we can do about it now. */
	case 0:
	    if (new_file(uf))
		byte_size = (flags & O_BINARY) ? 9 : 7 ;
	    else {
		byte_size = (_gtfdb(uf->uf_ch,_FBBYV) << FBBSZ_S) & FBBSZ_M;
		if (byte_size == 0 || byte_size == 36)
		    byte_size = (flags & O_BINARY) ? 9 : 7;
	    }
    }
    uf->uf_bsize = byte_size;			/* save for posterity */
    if (flags & O_T20_THAWED) open_mode |= OF_THW;	/* thawed access? */
/*
 *	if they explicitly request converted i/o, then propagate the flag,
 *	else if they DIDN'T explicitly request UNconverted (e.g. it's up to
 *	us to decide), and the file is being opened for text in 7-bit bytes,
 *	then go for conversion.
 */
    if ((flags & O_CONVERTED) ||
	(byte_size == 7 && !(flags & (O_UNCONVERTED | O_BINARY))))
	uf->uf_flgs |= UIOF_CONVERTED;

    arg_block[1] = uf->uf_ch;
    arg_block[2] = (byte_size << 30) + open_mode;
    if (!jsys(OPENF, arg_block))
	return 0;			/* Open failed */

    /* We won!  Now do special hack if dealing with a new file -- we
    ** quickly close and then re-open it, so that the file will then
    ** actually exist and can be seen by access(), stat(), GTFDB%, and
    ** everything else.  This compensates for the T20 braindamage where
    ** a new file doesn't really exist (certainly not permanently) until
    ** it is CLOSF%'d.
    */
    if (!new_file(uf))
	return 1;		/* Not a new file, we're already OK */
    arg_block[1] = uf->uf_ch | CO_NRJ;
    jsys(CLOSF, arg_block);	/* CLOSF% it, keeping JFN (ignore error) */
    arg_block[1] = uf->uf_ch;	/* Now OPENF% again (ac2 still has mode) */
    return jsys(OPENF, arg_block);
}
int _rljfn(jfn)
int jfn;
{
    int arg_block[5];

    arg_block[1] = jfn;
    return jsys(RLJFN, arg_block);
}
#endif /* SYS_T20+SYS_10X */
#if SYS_T10+SYS_CSI+SYS_WTS	/* TOPS-10/WAITS OS-dependent file opening code */

/* OPENSYS for TOPS-10/WAITS/CSI
*/
static int chopen(), ringinit();
static int o_lookup(), o_enter(), o_crupd(), o_out(), o_close();
static int o_open(), o_findeof();
static void flerset(), ringfree();

static int
opensys(uf, path, flags, mode)
struct _ufile *uf;
char *path;
int flags, mode;
{
    int err, devbits;
    int rwflag = 0;		/* R/W flags */
    int nbufs, bufsiz;
    struct _filehack f;

    /* Check out flags */
    switch (flags & (O_RDONLY | O_WRONLY | O_RDWR)) {
	case O_RDONLY:		/* Can read */
	    rwflag = UIOF_READ;
	    break;
	case O_WRONLY:		/* Can write */
	    rwflag = UIOF_WRITE;
	    break;
	case O_RDWR:
	    rwflag = UIOF_READ | UIOF_WRITE;
	    break;
	default:		/* Shouldn't happen */
	    return EINVAL;
    }
    if (!(rwflag & UIOF_WRITE))		/* If not writing, then ignore */
	flags &= ~(O_CREAT|O_TRUNC|O_APPEND|O_EXCL);	/* these flags */
    uf->uf_flgs |= rwflag | ((flags & O_APPEND) ? UIOF_APPEND : 0);	

    /* Now check bytesize, if any given */
    switch (flags & O_BSIZE_MASK) {
	case O_BSIZE_7: uf->uf_bsize = 7; break;
	case O_BSIZE_8: uf->uf_bsize = 8; break;
	case O_BSIZE_9: uf->uf_bsize = 9; break;
	case 0:		uf->uf_bsize = (flags & O_BINARY) ? 9 : 7; break;
	default: return EINVAL;		/* Bad arg */
    }
    uf->uf_nbpw = WORD_BIT / uf->uf_bsize;

    /* Decide whether to use converted I/O */
    if ((flags & O_CONVERTED) ||
	(uf->uf_bsize == 7 && !(flags & (O_UNCONVERTED | O_BINARY))))
	uf->uf_flgs |= UIOF_CONVERTED;

    /* Now attempt to parse the pathname */
    if (err = _fparse(&f.fs, path))
	return err;		/* Ugh, failed somehow */

    /* Parse won, set up other vars so we can OPEN device. */
    if (!(f.filop.fo_dev = f.fs.fs_dev))	/* If no device given, */
	f.filop.fo_dev = _SIXWRD("DSK");	/* use DSK: as default. */
    MUUO_ACVAL("DEVCHR", f.filop.fo_dev, &devbits);	/* Get dev bits */
    if (devbits == 0)
	return ENODEV;			/* Fail - "no such device" */

    if (devbits & uuosym("DV.DSK")) {
	/* Disk device gets special handling */
	uf->uf_type = UIO_DVDSK;
	uf->uf_flgs |= UIOF_BLKDEV;		/* Say block device */
	/* Decide whether we'll need dump mode or not (see t10io.doc!) */
	if ((rwflag&UIOF_WRITE)			/* Never if not writing */
	  && ( (rwflag&UIOF_READ)		/* Doing R/W (update) */
	    || !(flags&(O_CREAT|O_TRUNC|O_APPEND))	/* Special case 1 */
	    || !(flags&(O_TRUNC|O_APPEND|O_EXCL)) )) {	/* Special case 2 */
	    f.filop.fo_ios = uuosym(".IODMP");
	    f.filop.fo_brh = 0;			/* No buffer rings */
	    nbufs = 0;
	} else {				/* Plain R or W */
	    f.filop.fo_ios = uuosym(".IOIMG");
	    nbufs = 2;				/* Want 2 buffers */
	    bufsiz = UIO_T10_DSKWDS+3;		/* of this size */
	}
    } else {			
	/* Random device, probably TTY or something */
	f.filop.fo_ios = uuosym(".IOASL");	/* Use this in case terminal */
#if SYS_T10+SYS_CSI
	if (!MUUO_ACVAL("DEVSIZ", &f.filop.fo_ios, &bufsiz))
	    bufsiz = XWD(2,UIO_T10_DSKWDS+3);
	if (bufsiz <= 0)		/* If no such device or bad mode */
	    return ENXIO;		/* "no such device or address" */
	nbufs = FLDGET(bufsiz, _LHALF);	/* Get desired # buffers */
	bufsiz &= _RHALF;
#elif SYS_WTS
	WTSUUO_ACVAL("BUFLEN", f.filop.fo_dev, &bufsiz);
	if (bufsiz <= 0) bufsiz = UIO_T10_DSKWDS+1;
	bufsiz += 2;			/* Canonicalize to T10 value */
	nbufs = 2;
#endif
	if (bufsiz > (UIO_T10_DSKWDS+3))	/* Don't exceed our max */
	    bufsiz = UIO_T10_DSKWDS+3;		/* too big, use our max. */
    }

    /* Point to buffer rings we'll use if LOOKUP/ENTER succeeds. */
    if (nbufs) {
	f.filop.fo_brh = XWD(
		(rwflag&UIOF_WRITE ? (int)&uf->uf_boring : 0),
		(((rwflag&UIOF_READ) || (flags&O_APPEND))
				   ? (int)&uf->uf_biring : 0)
		);
    }

    if (err = chopen(uf, &f, flags))	/* Open the channel! */
	return _opnrel(uf), err;

    /* We won, channel is open!  Must now set up buffering, bletch.
    ** Note that input side may have been opened even if user didn't ask
    ** for it, in order to read last block.
    */
    if (nbufs == 0) {
	/* Using dump mode, set up just 1 buffer. */
	if (!_uiobuf(uf))
	    return _opnrel(uf), errno;
    } else {
	if (uf->uf_flgs&(UIOF_READ|UIOF_OREAD)) {
	    if (err = ringinit(uf, UIOF_READ, nbufs, bufsiz))
		return _opnrel(uf), err;
	}
	if (rwflag&UIOF_WRITE) {
	    if (err = ringinit(uf, UIOF_WRITE, nbufs, bufsiz))
		return _opnrel(uf), err;
	}
    }

    /* See whether to find true EOF or not (only applicable to block devs).
    ** For simplicity we always do this, even if read-only.  The alternative
    ** would be to set some flag for checking the last block when it's
    ** finally encountered during a read.
    */
    if (uf->uf_flgs & UIOF_BLKDEV) {
	if (err = o_findeof(uf))		/* Find true file EOF */
	    return _opnrel(uf), err;
	/* If write-only, buffered, and appending,
	** then we've got to preload the last block before closing the
	** input side of the channel!  After reading it in, it needs to
	** be copied into the output buffer.  All the flags will already
	** have been set correctly, however.
	*/
	if (nbufs && (uf->uf_flgs&UIOF_APPEND)
	  && !(uf->uf_flgs&UIOF_READ)
	  && uf->uf_flen > 0) {		/* Can also skip if file empty */
	    char *cp;
	    uf->uf_pos = uf->uf_flen;		/* Seek to EOF */
	    if (!_blkprime(uf, 0)		/* and ensure block read! */
	      || !(cp = uf->uf_boring.br_bp))	/* Get ptr to output blk */
		return _opnrel(uf), EIO;
	    /* Note bump of CP prior to use.  This is important! */
	    memcpy(++cp, uf->uf_bpbeg, uf->uf_blen);	/* Copy to out blk! */
	    uf->uf_bpbeg = cp;			/* This is new buffer start */
	}
	/* Clean up after seeks etc so initial pos always 0 even if
	** current block is somewhere else.
	*/
	uf->uf_pos = 0;
	uf->uf_rleft = uf->uf_wleft = uf->uf_eof = 0;
    }

#if !SYS_CSI	/* CSI doesn't support closing input side only!  Bletch... */
    /* If a user mode doesn't allow reading but we still have channel open
    ** for input, close the input side.  This can happen when setting
    ** things up for O_APPEND to a write-only file.
    ** Avoid doing so if using dump mode (to leave the 2 special cases alone).
    */
    if (nbufs && !(uf->uf_flgs&UIOF_READ) && (uf->uf_flgs&UIOF_OREAD)) {
	o_close(uf, uuosym("CL.OUT"));	/* Close input side of chan */
	ringfree(&uf->uf_biring);
    }
#endif

    /* Everything's OK, last thing is to remember the filespec we used
    ** to open this channel, so fstat() has a chance at doing something
    ** useful.  TOPS-10 has almost no ways of interrogating channel status!
    */
    uf->uf_fs = f.fs;		/* Copy filespec info directly */
    return 0;			/* Won, return without error! */
}
/* CHOPEN - Open TOPS-10 I/O channel as per flags.
**	The "OPEN" block (starting at f->filop.fo_ios) is already set up.
**	Returns 0 on success, else error code.
**	Sets uf_flen to file size (rounded up to words).
**	Sets UIOF_OREAD if channel still open for reading.
*/
static int
chopen(uf, f, flags)
struct _ufile *uf;
struct _filehack *f;
{
    int err;

    if (flags & O_CREAT) {	/* Create file if doesn't exist? */
	if (flags & O_EXCL) {	/* Error if file exists? */
	    if (o_lookup(uf, f)==0)
		return EEXIST;			/* Error - File exists */
	    if (uf->uf_flgs & UIOF_READ)	/* Doesn't exist, reading? */
		return o_crupd(uf, f);		/* Update a new file! */
	    else return o_enter(uf, f, _FOWRT);	/* Write a new file! */
	}
	if (flags & O_TRUNC) {	/* Truncate if file exists? */
	    if (uf->uf_flgs & UIOF_READ)	/* Doesn't exist, reading? */
		return o_crupd(uf, f);		/* Update a new file! */
	    else return o_enter(uf, f, _FOWRT);	/* Write a new file! */
	}
	if (uf->uf_flgs & UIOF_READ) {		/* R/W? */
/* fix up (ineff) */
	    if (o_lookup(uf, f)!=0)		/* R/W with C */
		return o_crupd(uf, f);		/* Create if doesn't exist */
	    return o_enter(uf, f, _FOSAU);	/* Now get W */
	}

	/* Just writing (with create if file non-ex) */
	if (o_lookup(uf, f)!=0)
	    return o_enter(uf, f, _FOWRT);	/* Non-ex, write new file */
	return o_enter(uf, f, _FOSAU);	/* Open for update, caller closes R */
    }

    /* Not creating, file must exist. */
    if (err = o_lookup(uf, f))
	return err;			/* Doesn't exist, fail */
    if (!(uf->uf_flgs & UIOF_WRITE))	/* If just reading, that was all! */
	return 0;
    if (flags & O_TRUNC) {		/* Truncate existing? */
	o_close(uf, 0);			/* Yes, close chan */
	uf->uf_flen = -1;		/* Forget about length */
	if (uf->uf_flgs & UIOF_READ)	/* Reading too? */
	    return o_crupd(uf, f);	/* Update a new file! */
	return o_enter(uf, f, _FOWRT);	/* Supersede existing file */
    }

    /* Writing existing file, no truncate. */
    return o_enter(uf, f, _FOSAU);	/* File exists, open for update */
}
static int
o_lookup(uf, f)
struct _ufile *uf;
struct _filehack *f;
{
    int len;

#if SYS_WTS
    if (len = o_open(uf, f, _FORED, LER_OLD))	/* Read, old-style LER block */
	return len;			/* Failed, return error */
    len = f->orb.s.rb_ppn;		/* Get word with length in LH */
    len >>= WORD_BIT/2;			/* Get signed LH */
    if (len > 0) len = -len;		/* Neg means # words */
    else len *= UIO_DSKWDS;		/* Pos means # blocks (ugh) */
#else
    if (len = o_open(uf, f, _FORED, LER_RBSIZ))	/* Read, new extended LER block */
	return len;			/* Failed, return error */
    len = f->xrb.s.rb_siz;		/* Get word with length in it */
#endif
    uf->uf_flen = uf->uf_nbpw * len;	/* Get file length in bytes */
    uf->uf_flgs |= UIOF_OREAD;		/* Say opened for reading! */
    return 0;
}

static int
o_enter(uf, f, op)
struct _ufile *uf;
struct _filehack *f;
{
    int err;
    if (err = o_open(uf, f, op, LER_OLD))	/* Write, old-style LER block */
	return err;
    if (uf->uf_flen < 0)		/* Won!  If length not already set, */
	uf->uf_flen = 0;		/* just make it 0 */
    return 0;
}

static int
o_crupd(uf, f)
struct _ufile *uf;
struct _filehack *f;
{
    int err;
    if (err = o_open(uf, f, _FOWRT, LER_OLD)) /* First create empty file */
	return err;
    /* Let's hope an OUT is unnecessary to create 0-length file! */
    o_close(uf, 0);
    if (err = o_open(uf, f, _FOSAU, LER_OLD))	/* Now re-open */
	return err;
    uf->uf_flen = 0;			/* Won, length known to be 0! */
    return 0;
}

static int
o_out(uf)
struct _ufile *uf;
{
    if (!_filopuse)
	return !MUUO_IO("OUT", uf->uf_ch, 0);
    else {
	int argblk = XWD(uf->uf_ch, uuosym(".FOOUT"));
	return MUUO_AC("FILOP.", XWD(1,(int)&argblk));
    }
}

/* O_CLOSE - Close channel, but don't release it; leaves UIOF_OCHAN set.
*/
static int
o_close(uf, bits)
struct _ufile *uf;
{
    struct _foblk fo;

    uf->uf_flgs &= ~UIOF_OREAD;		/* Will no longer be open for read */
    if (!_filopuse)
	return MUUO_IO("CLOSE", uf->uf_ch, bits);
    fo.fo_chn = uf->uf_ch;
    fo.fo_fnc = uuosym(".FOCLS");
    fo.fo_ios = bits;
    return MUUO_AC("FILOP.", XWD(2,(int)&fo));
}

static int
o_open(uf, f, op, lertyp)
struct _ufile *uf;
struct _filehack *f;
{
    int ret;

    f->error = -1;

    if (!_filopuse) {			/* Old-style UUOs */
	int rbadr;

	/* Must ensure device is OPENed, sigh */
	if (!(uf->uf_flgs&(UIOF_OREAD|UIOF_OCHAN))	/* If needed, */
	  && !MUUO_IO("OPEN", uf->uf_ch, &f->filop.fo_ios)) {	/* open dev! */
	    return ENXIO;		/* "No such device or address" */
	}
	uf->uf_flgs |= UIOF_OCHAN;	/* Remember channel opened */
	rbadr = (lertyp == LER_OLD)	/* Old or extended lookup blk */
			? (int)&f->orb : (int)&f->xrb;
	ret = 1;
	if ((op == _FORED || op == _FOSAU)	/* Read or update */
	  && !(uf->uf_flgs&UIOF_OREAD)) {	/* and not already open? */
	    flerset(f, &f->fs, lertyp);		/* Set up LER block */
	    if (ret = MUUO_IO("LOOKUP", uf->uf_ch, rbadr)) {
		uf->uf_flgs |= UIOF_OREAD;
	    }
	}
	if (ret && (op == _FOWRT || op == _FOSAU)) {	/* Write or update */
	    flerset(f, &f->fs, lertyp = LER_OLD);	/* Use old LER blk */
	    ret = MUUO_IO("ENTER", uf->uf_ch, rbadr);
	}

	if (!ret) f->error = (lertyp == LER_OLD)	/* Get err if any */
			? f->orb.s.rb_ext.rbe.err
			: f->xrb.s.rb_ext.rbe.err;
    } else {
	flerset(f, &f->fs, lertyp);		/* Set up LER block */
	f->filop.fo_chn = uf->uf_ch;
#if !SYS_CSI			/* Normal T10's must allocate, barf */
	if (uf->uf_ch > 017) f->filop.fo_chn = uuosym("FO.ASC");
#endif
	f->filop.fo_fnc = op;
	f->filop.fo_nbf = 0;
	f->filop.fo_leb = (lertyp == LER_OLD)
			? XWD(0,(int)&f->orb) : XWD(0,(int)&f->xrb);
	if (!MUUO_ACVAL("FILOP.", XWD(6,(int)&f->filop), &ret))
	    f->error = ret;
	else if (op == _FORED || op == _FOSAU)
	    uf->uf_flgs |= UIOF_OREAD;
#if !SYS_CSI			/* Normal T10 must use ext chan we got */
	if (f->error == -1 && uf->uf_ch > 017)	/* If no err & extended chn */
	    uf->uf_ch = f->filop.fo_chn;
#endif
    }
    return  (f->error == -1)
	? 0			/* Won */
	: _t10error(f->error);	/* Failed, analyze error */
}

/* O_FINDEOF - Find actual EOF of file (ugh bletch)
**	For 8-bit files on CSI, attempt last-word check.
**	For converted files (any size), flush trailing NULs in last word.
**	For all others, give up and accept current uf_flen.
**	Current position is left random, caller is expected to reset.
*/
static int
o_findeof(uf)
struct _ufile *uf;
{
    int adj;

    if (uf->uf_flen <= 0 || !(uf->uf_flgs & UIOF_OREAD))
	return 0;
    if (
#if SYS_CSI
         (uf->uf_bsize == 8) ||
#endif
         (uf->uf_flgs&UIOF_CONVERTED)) {
	uf->uf_pos = uf->uf_flen - 1;	/* Want last byte of file */
	if (!_blkprime(uf, 0))		/* Attempt prime for reading */
	    return EIO;			/* Ugh, error!! */
#if SYS_CSI
	if (uf->uf_bsize == 8) {
	    /* Do special CSI hack for 8-bit files; check bottom 4 bits
	    ** of last word in file, which contain the # of unused bytes.
	    */
	    if ((adj = (*(int *)uf->uf_cp) & 017) > 3)
		adj = 0;			/* If bad, use all 4 bytes */
	} else
#endif
	if (uf->uf_flgs & UIOF_CONVERTED) {
	    /* Flush trailing NUL chars from N-bit converted file */
	    for (adj = 0; *(uf->uf_cp) == '\0'; uf->uf_cp--)
		if (++adj >= uf->uf_nbpw) break;
	}
	uf->uf_flen -= adj;			/* Adjust file length! */
    }
    return 0;
}
/* _OPNREL - Close and release I/O channel.
**	Called when an open() fails and needs to clean up.
**	Also invoked by close().
*/
int
_opnrel(uf)
struct _ufile *uf;
{
    if (!_filopuse)
	MUUO_IO("RELEAS", uf->uf_ch, 0);	/* Flush channel */
    else {
	int argblk = XWD(uf->uf_ch, uuosym(".FOREL"));
	MUUO_AC("FILOP.", XWD(1,(int)&argblk));
    }
    if (uf->uf_buf) {			/* If had a block or conversion buf, */
	_iobfre(uf->uf_buf);		/* free up the buffer! */
	uf->uf_buf = NULL;
    }
    ringfree(&uf->uf_biring);		/* Free up input ring if one */
    ringfree(&uf->uf_boring);		/* ditto output ring */
    return 0;
}

static int
ringinit(uf, iof, nbufs, bufsiz)
struct _ufile *uf;
{
    register struct _iobring *br;
    register struct _iob *b1, *b2;

    /* Point to input or output ring header */
    br = (iof & UIOF_READ) ? &uf->uf_biring : &uf->uf_boring;

    if (!(b1 = _iobget()))		/* At least one buffer... */
	return EMFILE;			/* No memory for more files */
    if (nbufs >= 2) {			/* Want double-buffering? */
	if (!(b2 = _iobget()))
	    return EMFILE;		/* Out of mem again */
	b1->bf_nba = ((int)b2)+1;	/* Point buffers to each other */
	b2->bf_nba = ((int)b1)+1;
	b2->bf_iou = 0;			/* Set up 2nd buffer */
	b2->bf_siz = bufsiz - 2;
    } else
	b1->bf_nba = ((int)b1)+1;	/* Point single buffer to itself */
    b1->bf_iou = 0;			/* Set up 1st buffer */
    b1->bf_siz = bufsiz - 2;

    /* Buffers all set up and linked together, now set up the
    ** buffer ring header (which evidently is not initialized at all by the
    ** the OPEN UUO).
    ** One thing we do is set the byte size of the BP into the buffer;
    ** but NOTE CAREFULLY that the monitor routines, when they initialize
    ** a buffer, ALWAYS zero out the P field of the BP and set the RH
    ** to point at the word before the actual start of data.  This is
    ** OK if a IDPB/ILDB is done, but is a screw for ADJBP because that
    ** preserves byte alignment!!!!!!  So anything that uses br_bp has
    ** to be aware of this.... ack ugh bletch barf.
    */
    br->br_use = 1;		/* Say no I/O yet */
    br->br_cnt = 0;		/* Clear just to be safe */
    br->br_buf = ((int)b1)+1;	/* Point ring header to 1st buffer */
    switch (uf->uf_bsize) {	/* Set up byte pointer with right bytesize */
	case 7:
	case 8:
	case 9:			/* Put bytesize into S field of byte ptr */
	    br->br_bp = (char *) ((uf->uf_bsize << 24)
		| (((int)(int *)b1->b_data)-1) );
	    break;
	default:
	    return ENXIO;	/* Bad bytesize */
    }
    if (iof == UIOF_READ) {
	/* Set up for buffered input using either block or char device.
	** Block devs look at uf_rleft, uf_cp, uf_blen.
	** Char devs just look at uf_biring.
	*/
	uf->uf_blen = uf->uf_nbpw * (bufsiz - 3);
	uf->uf_rleft = 0;
    } else {
	/* Set up for buffered output using either block or character device.
	** Block devices look at uf_wleft, uf_cp, and uf_blen.
	** Character devices just look at the uf_boring header.
	*/
	if (o_out(uf) <= 0)	/* Init buffers */
	    return EIO;		/* Some error on 1st out */
	uf->uf_blen = uf->uf_boring.br_cnt;	/* Remember # bytes avail! */
	uf->uf_bpbeg = uf->uf_boring.br_bp;

	/* Make CP and get right alignment!!!  Stupid T10 monitor always gives
	** us a byte pointer with a zero P field, which is incorrectly
	** aligned for most kinds of bytes.  Bumping this by one both gets
	** us a char pointer and restores proper alignment.
	** If this ever assembles into ADJBP instead of <IBP 0,> we lose big!
	*/
	++(uf->uf_bpbeg);
	uf->uf_wleft = 0;		/* Force init on blk-dev write */
    }
    return 0;			/* Success */
}


static void
ringfree(br)
register struct _iobring *br;		/* T10 I/O buffer ring header */
{
    struct _iob *b, *bf, *bx;

    if (br->br_buf) {			/* If have a buffer ring...  */
	bf = b = (struct _iob *)(br->br_buf-1);	/* Original ptr */
	do {
	    bx = (struct _iob *)(bf->bf_nba-1);	/* Remember next */
	    _iobfre(bf);			/* then flush buf */
	} while ((bf = bx) != b);		/* loop unless next is orig */
	br->br_buf = NULL;
	br->br_cnt = 0;				/* Clear just in case */
	br->br_bp = 0;
    }
}
/* _FLOOKUP -  T10/WAITS auxiliary file lookup routine.
**	This routine is only used by stat() to get information about a file,
**	thus it releases the channel immediately after opening.
** This serves the same function as _GTJFN does for T20; it gets a
** handle on a file (here a channel #) without any intention of doing I/O.
** The UF is not seized and so may be re-used (possibly by a signal handler,
** so be careful to have ints disabled).
** Returns 0 if succeeded, else an error number.
*/
int
_flookup(file, f, lertyp)
char *file;		/* Filename.  If NULL, assumes already parsed. */
struct _filehack *f;
int lertyp;		/* LER block type to use */
{
    struct _ufile *uf;
    int err;

    /* Say no lookup error so far */
    f->error = -1;		/* Must distinguish from ERFNF% (0), sigh. */

    /* First parse the filespec, if desired.  May be NULL to just use f->fs. */
    if (file && (err = _fparse(&f->fs, file)))
	return err;
    if (!(uf = _ufcreate()))	/* Find a free channel # */
	return EMFILE;		/* Too many files */

    /* Set up device and mode */
    f->filop.fo_ios = 0;		/* Simply zap all flags & mode */
    f->filop.fo_brh = 0;		/* Use no buffering */
    f->filop.fo_dev = f->fs.fs_dev ? f->fs.fs_dev : _SIXWRD("DSK");

    if (err = o_open(uf, f, _FORED, lertyp)) {	/* Open for read */
	_opnrel(uf);			/* Failed, flush channel used */
	return err;			/* Return error */
    }

    /* Have the info we wanted, now close channel ASAP */
    if (!_filopuse)
	MUUO_IO("RELEAS", uf->uf_ch, 0);
    else {
	int arg = XWD(uf->uf_ch, uuosym(".FOREL"));
	MUUO_AC("FILOP.", XWD(1,(int)&arg));
    }
    return 0;				/* Won, return success! */
}

/* _FRENAME -  T10/WAITS auxiliary file rename routine.
**	This is common code used by the USYS calls: unlink() and rename().
**	It is located here because of its heavy dependence on LOOKUP.
** Returns 0 if succeeded, else an error number.
** Always releases the channel.
*/
/* TO BE DONE: FIX ATTRIBUTE COPY (so they aren't all zapped to 0)??? */
int
_frename(fsold, fsnew)
struct _filespec *fsold, *fsnew;
{
    struct _filehack f, nf;
    struct _ufile *uf;

    if (!(uf = _ufcreate()))	/* Find a free channel # */
	return EMFILE;		/* Too many files */
    f.filop.fo_chn = uf->uf_ch;	/* Remember channel # we got */

    f.error = -1;
    f.filop.fo_ios = 0;		/* Simply zap all flags & mode */
    f.filop.fo_brh = 0;		/* Use no buffering */
    f.filop.fo_dev = fsold->fs_dev ? fsold->fs_dev : _SIXWRD("DSK");
    flerset(&f, fsold, LER_OLD);	/* Use short LER blk for old fspec */
    flerset(&nf, fsnew, LER_OLD);	/* Another for new filespec */
    if (!_filopuse) {
	if (!MUUO_IO("OPEN", f.filop.fo_chn, &f.filop.fo_ios))
	    return ENXIO;	/* "No such device or address" (sigh) */
	if (!MUUO_IO("LOOKUP", f.filop.fo_chn, &f.orb))
	    f.error = f.orb.s.rb_ext.rbe.err;
	else if (!MUUO_IO("RENAME", f.filop.fo_chn, &nf.orb))
	    f.error = nf.orb.s.rb_ext.rbe.err;
	MUUO_IO("RELEAS", f.filop.fo_chn, 0);
    } else {
	int ret;
#if !SYS_CSI
	if (f.filop.fo_chn > 017)	/* Normal T10's must allocate, barf */
	    f.filop.fo_chn = uuosym("FO.ASC");
#endif
	f.filop.fo_fnc = fsnew->fs_nam ? uuosym(".FORNM") : uuosym(".FODLT");
	f.filop.fo_nbf = 0;
	f.filop.fo_leb = XWD((int)&nf.orb,(int)&f.orb);
	if (!MUUO_ACVAL("FILOP.", XWD(6,(int)&f.filop), &ret))
	    f.error = ret;
	f.filop.fo_fnc = uuosym(".FOREL");
	MUUO_AC("FILOP.", XWD(1,(int)&f.filop));
    }
    return (f.error == -1) ? 0 : _t10error(f.error);
}

static void
flerset(f, fs, lertyp)
struct _filehack *f;
struct _filespec *fs;
{
    /* First determine right directory path to use (0 or PPN or path spec) */
    f->lerppn = (fs->fs_nfds > 1)	/* More than one dir given? */
		? (int)&fs->fs_path		/* Yeah, just point to it */
		: fs->fs_path.p_path.ppn;	/* No, just use PPN (or 0) */
    fs->fs_path.p_arg = 0;			/* Clear this just in case */

    /* Now set up LER block from filespec as appropriate */
    if (lertyp == LER_OLD) {
	f->orb.s.rb_nam = fs->fs_nam;
	f->orb.s.rb_ext.wd = fs->fs_ext;
	f->orb.s.rb_prv.wd = 0;
	f->orb.s.rb_ppn = f->lerppn;
    } else {
#if SYS_WTS
	_panic("flerset: extended fmt");	/* Not supported on WAITS */
#else
	/* Want extended block, set that up.  If caller plans to use it
	** with ENTER, it should zero f->xrb beforehand because of all
	** the additional values that the monitor might take from the
	** extra words.
	*/
	f->xrb.s.rb_cnt = lertyp;		/* # wds following */
	f->xrb.s.rb_ppn = f->lerppn;
	f->xrb.s.rb_nam = fs->fs_nam;
	f->xrb.s.rb_ext.wd = fs->fs_ext;
	f->xrb.s.rb_prv.wd = 0;
#endif
    }
}
/* LOOKUP/ENTER/RENAME/GETSEG/RUN ERROR CODES */
#define Eunk	EIO	/* No obvious mapping, maybe try again later */

static short t10errs[] = {
	ENOENT,	/* ERFNF% - FILE NOT FOUND */
	ENOTDIR,	/* ERIPP% - INCORRECT PPN */
	EACCES,	/* ERPRT% - PROTECTION FAILURE */
	EBUSY,	/* ERFBM% - FILE BEING MODIFIED */
	EEXIST,	/* ERAEF% - ALREADY EXISTING FILE NAME */
	EFAULT,	/* ERISU% - ILLEGAL SEQUENCE OF UUOS */
#if SYS_WTS
	EFAULT, EFAULT, EIO, EIO, ENOSPC
#else
	EIO,	/* ERTRN% - TRANSMISSION ERROR */
	ENOEXEC,	/* ERNSF% - NOT A SAVE FILE */
	ENOMEM,	/* ERNEC% - NOT ENOUGH CORE */
	ENXIO,	/* ERDNA% - DEVICE NOT AVAILABLE */
	ENODEV,	/* ERNSD% - NO SUCH DEVICE */
	Eunk,	/* ERILU% - ILLEGAL MONITOR CALL FOR GETSEG OR FILOP, OR SAVE. */
	EDQUOT,	/* ERNRM% - NO ROOM */
	EROFS,	/* ERWLK% - WRITE-LOCKED */
	ENFILE,	/* ERNET% - NOT ENOUGH TABLE SPACE */
	Eunk,	/* ERPOA% - PARTIAL ALLOCATION */
	Eunk,	/* ERBNF% - BLOCK NOT FREE */
	EISDIR,	/* ERCSD% - CAN'T SUPERSEDE A DIRECTORY */
	ENOTEMPTY,	/* ERDNE% - CAN'T DELETE NON-EMPTY DIRECTORY */
	ENOENT,	/* ERSNF% - SFD NOT FOUND */
	ENOENT,	/* ERSLE% - SEARCH LIST EMPTY */
	ELOOP,	/* ERLVL% - SFD NEST LEVEL TOO DEEP */
	Eunk,	/* ERNCE% - NO-CREATE FOR ALL S/L */
	ETXTBSY,	/* ERSNS% - SEGMENT NOT ON SWAP SPACE OR JOB LOCKED */
	EPERM,	/* ERFCU% - CAN'T UPDATE FILE */
	EFAULT,	/* ERLOH% - LOW SEG OVERLAPS HI SEG (GETSEG) */
	EACCES,	/* ERNLI% - NOT LOGGED IN (RUN, SAVE) */
	ETXTBSY,	/* ERENQ% - FILE STILL HAS OUTSTANDING LOCKS SET */
	ENOEXEC,	/* ERBED% - BAD .EXE FILE DIRECTORY (GETSEG,RUN) */
	ENOEXEC,	/* ERBEE% - BAD EXTENSION FOR .EXE FILE(GETSEG,RUN) */
	ENOEXEC,	/* ERDTB% - .EXE DIRECTORY TOO BIG(GETSEG,RUN,SAVE.) */
	Eunk,	/* ERENC% - TSK - EXCEEDED NETWORK CAPACITY */
	Eunk,	/* ERTNA% - TSK - TASK NOT AVAILABLE */
	Eunk,	/* ERUNN% - TSK - UNDEFINED NETWORK NODE */
	Eunk,	/* ERSIU% - RENAME - SFD IS IN USE */
	Eunk,	/* ERNDR% - DELETE - FILE HAS AN NDR LOCK */
	Eunk,	/* ERJCH% - JOB COUNT HIGH (A.T. READ COUNT OVERFLOW) */
	Eunk,	/* ERSSL% - CANNOT RENAME SFD TO A LOWER LEVEL */
	EBADF,	/* ERCNO% - CHANNEL NOT OPENED (FILOP.) */
	ENXIO,	/* ERDDU% - DEVICE "DOWN" AND UNUSEABLE */
	EACCES,	/* ERDRS% - DEVICE IS RESTRICTED */
	EACCES,	/* ERDCM% - DEVICE CONTROLLED BY MDA */
	EPERM,	/* ERDAJ% - DEVICE ALLOCATED TO ANOTHER JOB */
	EIO,	/* ERIDM% - ILLEGAL I/O DATA MODE */
	EINVAL,	/* ERUOB% - UNKNOWN/UNDEFINED OPEN BITS SET */
	EBUSY,	/* ERDUM% - DEVICE IN USE ON AN MPX CHANNEL */
	ENFILE,	/* ERNPC% - NO PER-PROCESS SPACE FOR EXTENDED I/O CHANNEL TABLE */
	EMFILE,	/* ERNFC% - NO FREE CHANNELS AVAILABLE */
	EINVAL,	/* ERUFF% - UNKNOWN FILOP. FUNCTION */
	EMFILE,	/* ERCTB% - CHANNEL TOO BIG */
	EINVAL,	/* ERCIF% - CHANNEL ILLEGAL FOR SPECIFIED FUNCTION */
	EFAULT,	/* ERACR% - ADDRESS CHECK READING ARGUMENTS */
	EFAULT,	/* ERACS% - ADDRESS CHECK STORING ANSWER */
	EINVAL,	/* ERNZA% - NEGATIVE OR ZERO ARGUMENT COUNT */
	EINVAL,	/* ERATS% - ARGUMENT BLOCK TOO SHORT */
	Eunk,	/* ERLBL% - MAGTAPE LABELING ERROR */
	Eunk,	/* ERDPS% - DUPLICATE SEGMENT IN ADDRESS SPACE */
	Eunk,	/* ERNFS% - NO FREE SECTION (SEGOP.) */
	Eunk,	/* ERSII% - SEGMENT INFORMATION INCONSISTENT (SEGMENT # AND NAME DON'T MATCH) */
	
#endif
};

int
_t10error(err)
{
    if (err < 0 || err > (sizeof(t10errs)/sizeof(t10errs[0])))
	return EIO;		/* for now */
    return t10errs[err];
}
/* T10/WAITS filespec parser
**	This parser is able to handle filename strings which are in standard
** TOPS-10 format, or UNIX format, or a combination thereof.
** The general syntax is:
**	 {node::} {dev:} {[dirpath]} {upath} {filename} {[dirpath]}
**
**	node - a TOPS-10 network site name (single word).
**	dev -	Device or logical structure name (single word).
**	[dirpath] - a TOPS-10 directory path, syntax: [P,PN{,sfds}]
**			If present, must always	contain the UFD (PPN).
**			It may contain SFDs after the PPN.
**			Only one [dirpath] may be given.
**	upath - a UNIX-style directory path, syntax: {/}{upath | name}/
**	filename - Syntax is: name{. | .ext}
**			i.e. "name", "name.", or "name.ext"
**
** The "upath" syntax has these peculiarities:
**	- An absolute dirpath (begins with "/") must have a PPN after the "/".
**		This PPN must not be enclosed in brackets.
**	- A PPN has the syntax "n,n" on TOPS-10; "nam,nam" on WAITS.
**		Due to possible future ambiguity ("n/n"), it is a bad
**		idea to use a PPN as the first part of a relative pathname.
**	- A relative dirpath is relative to the "default directory path"
**		as returned by PATH. if there is no device, or the "current
**		path" for the specified device.
**	- "." and ".." can be given as directory names.
**	- A upath can be combined with a [dirpath], however the upath cannot
**		start with a '/' or a PPN; it is considered to be relative
**		to [dirpath].
**	- It would be possible for "/dev/foo" to become FOO:, but it's hard
**		to think of a plausible use for this.
** Name components can be quoted in the following ways:
**	- System-independent quote char "\" quotes next char.
**	- System-dependent forms:
**		WAITS:	^A (down-arrow) encloses quoted word (no doubling)
**		T10:	" encloses quoted word (double to quote ")
**		CSI:	' or " enclose quoted word (double to quote ' or ")
** Incomplete PPNs can be specified:
**	A PPN of the form [,123] or [123,] or [,] will take the unspecified
**	fields from either the logged-in PPN (on T10, CSI) or the connected
**	disk PPN (on WAITS).  WAITS also permits [] or [FOO] to be the
**	same as [,] and [FOO,].
*/

static int ppnparse();
static char *getwd6();
static unsigned sixbit();
static int rjust6(), sixoct();

struct word6 {
    int len;		/* # chars actually in word */
    unsigned word;	/* word in SIXBIT, left justified */
    int term;		/* Character terminating word */
    int quoted;		/* Non-zero if anything was quoted in word */
};

#ifndef NFSCOMPS
#define NFSCOMPS 20
#endif

int
_fparse(f, path)
struct _filespec *f;
char *path;
{
    register struct word6 *wp;
    register int n;
    struct word6 wd[NFSCOMPS];		/* Parsed array of words */
    char *cp;
    struct word6 *beg, *end,		/* Terminator markers */
	*opnbrk = 0, *clsbrk = 0,
	*slash = 0, *firstslash = 0,
	*colon = 0, *dot = 0;
    int absupath;			/* TRUE if {dev:}/path... */

    memset((char *)f, 0, sizeof(*f));	/* Clear entire structure */

    /* Fill up parse array with words. */
    if (!(cp = path))
	return EINVAL;
    --cp;				/* Back up for PDP-10 efficiency */
    wp = beg = &wd[0];
    end = &wd[NFSCOMPS-1];		/* Last valid array entry */
    for (; cp = getwd6(cp, wp);) {
	switch (wp->term) {
	case '[':	if (opnbrk) return EINVAL;
		 	opnbrk = wp;	break;
	case ']':	if (clsbrk) return EINVAL;
			clsbrk = wp;	break;
	case '.':	if (dot)    return EINVAL;
			dot = wp;	break;
	case '/':	if (!firstslash) firstslash = wp;
			slash = wp;	break;
	case ':':	colon = wp;	break;
	}
	if (++wp >= end)	/* Check # components, leave 1 at end */
	    return ENAMETOOLONG;	/* File name too long */
    }
    if (wp->term)			/* If stopped due to parse error, */
	return wp->term;		/* return that error. */
    end = wp;				/* Done, remember last word we got */
    if (end->word == 0)			/* If last thing has nothing, */
	--end;				/* back up to simplify things */
    if (beg == end && !beg->word)	/* If nothing parsed, */
	return ENOENT;			/* return "No such file" */

    /* OK, now examine results! */
#if SYS_T10+SYS_CSI				/* 1st component "node::"? */
    if (colon) {
	if (beg->term != ':')		/* 1st thing better have colon term */
	    return EINVAL;
	if (beg < end && (beg+1)->term == ':' && (beg+1)->word==0) {
	    f->fs_nod = beg->word;	/* Yup, remember node name */
	    beg += 2;			/* and move on to next component */
	    if (colon < beg)		/* If that took care of last colon */
		colon = NULL;		/* needn't check for device. */
	}
    }	
#endif
    if (colon) {		/* 1st component now "dev:"? */
	if (colon != beg	/* Could be, check... */
	  || beg->term != ':'	/* Avoid stuff like "foo/bar:" or ":foo" */
	  || beg->word == 0)
	    return EINVAL;	/* Bad device spec */
	f->fs_dev = beg->word;	/* OK, get it! */
	if (++beg > end)	/* Move over it, and return if no more */
	    return 0;		/* win! */
    }

    /* Set flag if starts with absolute unix-style path */
    absupath = (firstslash == beg && beg->word==0);

    /* See whether we have a dreaded PPN and hack it if so */
    if (opnbrk || clsbrk) {	/* If have either of PPN delims, check. */
	if (!opnbrk || !clsbrk	/* unbalanced? */
	  || opnbrk >= clsbrk	/* backwards? */
	  || absupath		/* Conflicts with absolute unix path? */
	  || (clsbrk != end	/* Not at end of string */
	     && (opnbrk != beg	/*   or at beginning?  (if at beg, ensure */
		|| opnbrk->word)))	/* no preceding component) */ 
	    return EINVAL;
    } else if (absupath) {
	/* We have an absolute unix-style pathname, "/ppn ..." */
	opnbrk = beg;
	if (firstslash == slash) {		/* Allow "/ppn" alone */
	    clsbrk = end;
	    slash = NULL;
	} else {				/* Must be "/ppn/..." */ 
	    for (clsbrk = beg; (++clsbrk)->term != '/'; )
		if (clsbrk >= end)
		    return EINVAL;		/* Didn't find it??? */
	    if (slash <= clsbrk)		/* If this was last slash, */
		slash = NULL;			/* forget it, no upath. */
	}
    }
    if (opnbrk) {
	/* Seems OK, so hand off to a PPN parser. */
	if (n = ppnparse(f, opnbrk, clsbrk))
	    return n;			/* Failed, error of some kind. */

	/* Won, so take PPN off string (either at beg or end) */
	if (opnbrk == beg) beg = clsbrk+1;
	else end = opnbrk;
	if (beg > end)			/* If that was all, */
	    return 0;			/* we're done, win! */
    }

    /* beg-end inclusive now contain upath and/or filename.
    ** Check for existence of a upath (has slash) and handle if so.
    ** If PPN was parsed, f->fs_nfds will have # of directories
    ** that were already gobbled as part of absolute path.
    ** If no PPN was parsed, we need to ask monitor for current abs path.
    */
    if (slash) {		/* Have unix-style relative dir path? */
	if (slash < beg || slash > end)
	    return EINVAL;		/* Some weird syntax error */
	if (!(n = f->fs_nfds)) {	/* Already have abs part? */
	    /* No, must ask monitor where we currently are. */
#if SYS_T10+SYS_CSI
	    /* Ask for default directory path */
	    f->fs_path.p_arg = f->fs_dev ? f->fs_dev
				: uuosym(".PTFRD");
	    if (!MUUO_AC("PATH.",
		    XWD(sizeof(f->fs_path)/sizeof(int), (int)&f->fs_path)))
		return ENOENT;		/* Some problem... */
	    if (!f->fs_path.p_path.ppn)	/* ugh!! */
		return ENOENT;
	    for (n = 0; f->fs_path.p_path.sfd[n]; ++n);	/* # SFDs */
	    n++;			/* Plus one for UFD */
#elif SYS_WTS			/* Find current disk PPN */
	    WTSUUO_ACVAL("DSKPPN", 0, &(f->fs_path.p_path.ppn));
	    n = 1;
#endif 
	    f->fs_nfds = n;		/* Update count in filespec */
	}

	/* Now parse relative path, adding onto end of abs path. */
	--n;				/* Find # of SFDs so far */
	for (;; ++beg) {
	    if (beg->term != '/')
		break;
	    if (beg->word == 0)		/* "...//..." is OK, just ignore */
		continue;
	    if (beg->word == _SIXWRD(".")	/* Ditto "/./" */
		&& !beg->quoted)
		continue;
	    if (beg->word == _SIXWRD("..")
		&& !beg->quoted) {
		if (--n < 0) return EINVAL;	/* Don't allow "/ppn/../" */
		f->fs_path.p_path.sfd[n] = 0;	/* Restore terminating 0 */
		--(f->fs_nfds);
		continue;
	    }
	    if (n >= UIO_NSFDS)		/* Check for exceeding nest depth */
		return ENAMETOOLONG;
	    f->fs_path.p_path.sfd[n] = beg->word;
	    ++n;			/* Bump # of SFDs */
	    ++(f->fs_nfds);		/* and # of all FDs */
	}
	if (beg <= slash)		/* Make sure that took care of all */
	    return EINVAL;		/* slashes we saw! */
	if (beg > end)
	    return 0;			/* That's all, win! */
    }


    /* beg-end inclusive now should contain filename only.
    ** We may have any of these cases:
    **		1 word:  "name."  "name[" "name"
    **		2 words: "name. ext"  ". ext"
    **			 "name. ext[" ". ext["
    */
    switch (end-beg) {
	case 0:		/* One word */
	    if (beg->term != '.'
	      && beg->term != '['
	      && beg->term)
		return EINVAL;
	    f->fs_nam = beg->word;
	    break;
	case 1:		/* Two words */
	    if (beg->term != '.'
	      || (end->term != '['
		&& end->term != 0))
		return EINVAL;
	    f->fs_nam = beg->word;
	    f->fs_ext = end->word;
	    if (f->fs_ext & _RHALF)
		return ENAMETOOLONG;	/* Extension too long */
	    break;
	default:
	    return EINVAL;		/* Some syntax error */
    }

    return 0;				/* Won!! */    
}
/* T10 File parsing utilities */

/* PPNPARSE - parse a PPN, with optional dirpath.
**	"beg" points to the token BEFORE the PPN; its terminator will be
**		either '/' or '['.
**	"end" points to the last token of the PPN; its terminator should be
**		one of '/', ']', or NUL.
** Returns zero if parse succeeded, else an error number to be passed to user.
*/
static int
ppnparse(f, beg, end)
struct _filespec *f;
struct word6 *beg, *end;
{
    int term, cnt;
    int lh, rh;			/* Two halves of PPN */
    static int defppn = 0;	/* Default PPN if halves missing */

    cnt = end - beg;		/* Find # words in dirpath */
#if SYS_WTS
    if (cnt < 1)		/* On WAITS, permit [] or [FOO] */
#else
    if (cnt < 2)		/* Normal TOPS-10, 2 words always required! */
#endif
	return EINVAL;
    switch (term = beg->term) {
	case '[':		/* Standard PPN/dirpath syntax */
	    term = ']';		/* must end with this terminator */
	    break;
	case '/':		/* upath syntax must end with this one */
	case 0:			/* Permit "/23,45" case (NUL terminator) */
	    if (cnt > 2)	/* But both are restricted to 2 wds only! */
		return ENAMETOOLONG;
	    break;
	default:
	    return EINVAL;	/* Bad PPN starting delimiter */
    }
    if (end->term != term)	/* Last word must have proper terminator */
	return EINVAL;
    ++beg;
    if (cnt > 1 && beg->term != ',')	/* 1st wd of 2 must end in comma */
	return EINVAL;		/* Ugh, bad syntax */

    /* Right-justify the first two SIXBIT words */
    lh = rjust6(beg->word);
    if (cnt > 1) rh = rjust6((++beg)->word);

    if ((!lh || !rh) && !defppn)	/* If will need default PPN, get it. */
#if SYS_T10+SYS_CSI
	MUUO_VAL("GETPPN", &defppn);	/* Use logged-in PPN as default */

    /* Re-interpret the two SIXBIT words as octal numbers */
    if (!lh) lh = FLDGET(defppn, _LHALF);
    else lh = sixoct(lh);		/* Will return -1 if bad number */
    if (!rh) rh = FLDGET(defppn, _RHALF);
    else rh = sixoct(rh);
    if (lh < 0 || rh < 0)
	return EINVAL;			/* Non-numeric PPN */
#elif SYS_WTS
	WTSUUO_VAL("DSKPPN", &defppn);	/* Use connected PPN as default */
    if (!lh) lh = FLDGET(defppn, _LHALF);
    if (!rh) rh = FLDGET(defppn, _RHALF);
    if ((lh & _LHALF) || (rh &_LHALF))	/* Ensure wds <= 3 chars long */
	return ENAMETOOLONG;		/* Ugh */
#endif
    f->fs_path.p_path.ppn = XWD(lh,rh);
    f->fs_nfds = 1;			/* Say PPN parsed */

    /* Now see if any SFDs follow the PPN */
    if (cnt > 2) {
	register int *ip;
	if ((cnt -= 2) > UIO_NSFDS)
	    return ENAMETOOLONG;	/* Too many SFDs */
	ip = &(f->fs_path.p_path.sfd[0]);
	while (--cnt >= 0) {
	    ++beg;
	    if (cnt && beg->term != ',')
		return EINVAL;		/* Non-comma SFD separator! */
	    *ip++ = beg->word;		/* Store SFD name */
	    ++(f->fs_nfds);		/* Bump count of dirs seen */
	}
    }
    return 0;
}

/* RJUST6 - right-justify a SIXBIT value */
static int
rjust6(wd)
register unsigned wd;
{
    if (wd)
	while ((wd & 077) == 0)
	    wd >>= 6;
    return wd;
}

/* SIXOCT - Interpret a right-justified SIXBIT word as an octal number,
**	and return the value.  Returns -1 if non-digit sixbit char seen.
*/
static int
sixoct(wd)
unsigned wd;
{
    register int digit;
    if (!wd)
	return wd;
    digit = (wd&077) - 020;
    if (digit >= 0 && digit <= 7
      && (digit += ((unsigned)sixoct(wd>>6)<<3)) >= 0)
	return digit;
    return -1;
}

#define tosixbit(c) (((c & 0100) ? c|040 : c&~040)&077)	/* Convert to SIXBIT */

/* GETWD6 - Scan a SIXBIT word, sets word6 struct and returns updated char ptr.
**	Note special hacking for "." and ".." -- they are recognized only
**	where meaningful.  The caller can distinguish between these and
**	explicitly quoted names by checking the "quoted" flag.
*/
static char *
getwd6(cp, wp)
register char *cp;
struct word6 *wp;
{
    register int c, n = 0, shift = 36;
    register unsigned wd = 0;

    wp->quoted = 0;
    for (;;) {
      switch (c = *++cp) {
	case '.':			/* Special delimiter */
	    if (n == 0 && strchr("./[", cp[1])) {	/* Chk . / [ or NUL */
		if (cp[1] == '.' && strchr("/[", cp[2])) {
		    wp->word = _SIXWRD("..");
		    c = *(cp += (n = 2));
		} else if (strchr("/[", cp[1])) {
		    wp->word = _SIXWRD(".");
		    c = *(cp += (n = 1));
		}
	    }
	case '\0':			/* Is char a normal delimiter? */
	case ':':	case '/':
	case '[':	case ']':
	case ',':
	    break;			/* Yup, break out of loop to return */

#if UIO_FSWORDQUOT1
	case UIO_FSWORDQUOT1:
#if UIO_FSWORDQUOT2
	case UIO_FSWORDQUOT2:
#endif
	    if (n)		/* If in middle of word, error. */
		return wp->term = ENOENT, (char *)NULL; /* Bad filespec. */

	    for (;;) {		/* Start gobbling a quoted word. */
		if (!*++cp)		/* If string ended before 2nd quote, */
		    return wp->term = ENOENT, (char *)NULL; /* Bad filespec. */
		if (*cp == c		/* Matches original quote char? */
		  && *++cp == c)	/* Yes, is it doubled? */
		    break;		/* Yes, so stop loop!  Else put in. */
		if (++n <= 6)		/* If have room, add to SIXBIT */
		    wd |= (unsigned) tosixbit(*cp) << (shift -= 6);
	    }

	    /* Check char following wd, must be valid terminator! */
	    if (strchr(".[,]/:", (c = *cp)) == NULL)
		return wp->term = ENOENT, (char *)NULL;	/* Bad filespec. */
	    break;			/* Won, break from loop. */
#endif

#if UIO_FSCHARQUOT1
	case UIO_FSCHARQUOT1:
#if UIO_FSCHARQUOT2
	case UIO_FSCHARQUOT2:		/* If it's quote char, */
#endif
	    c = *++cp;			/* ignore it and always use next */
	    wp->quoted++;
#endif
	default:
	    if (iscntrl(c)) {		/* Don't allow control chars */
	case ' ':			/* or unquoted spaces */
		wp->term = ENOENT;	/* Bad filespec, barf. */
		return NULL;
	    }
	    if (++n <= 6)		/* If have room, add to SIXBIT */
		wd |= (unsigned) tosixbit(c) << (shift -= 6);
	    continue;
      }
      break;	/* Breaking from switch also breaks from loop */
    }
    wp->len = n;			/* Won, return goodies */
    wp->word = wd;
    wp->term = c;
    return (c ? cp : NULL);
}

static unsigned
sixbit(s)
register char *s;
{
    register int c, n = 36;
    register unsigned wd = 0;

    c = *s;
    do {
	wd |= (unsigned)tosixbit(c) << (n -= 6);
    } while ((c = *s++) && n >= 6);
    return wd;
}
#endif /* SYS_T10+SYS_CSI+SYS_WTS */
#if SYS_ITS	/* ITS OS-dependent file opening code */

static void cvt_filename();	/* Convert filename for SOPEN */

/* OPENSYS for ITS
*/
static int
opensys(uf, path, flags, mode)
struct _ufile *uf;
char *path;
int flags, mode;
{
    int bits;
    char realpath[FILENAME_SIZE+1];
    char *realpath_ptr = realpath-1;

    /* O_NDELAY: don't hang mode ignored for now */
    /* Bits other than the ones listed here are not handled. */
    switch (flags & ~(O_BINARY | O_CONVERTED | O_UNCONVERTED | O_BSIZE_MASK |
			O_NDELAY | O_ITS_IMAGE | O_ITS_NO_IMAGE)) {
	case (O_RDONLY):
	    bits = SC_IMC(_UAI);
	    uf->uf_flgs |= UIOF_OLD;	/* If we win, it will be old! */
	    break;
	case (O_WRONLY | O_CREAT | O_TRUNC):
	    bits = SC_IMC(_UAO);
	    break;
	default: return EINVAL;
    }

    switch (flags & O_BSIZE_MASK) {
	case O_BSIZE_7: uf->uf_bsize = 7; break;
	case O_BSIZE_8: uf->uf_bsize = 8; break;
	case O_BSIZE_9: uf->uf_bsize = 9; break;
	default: uf->uf_bsize = (flags & O_BINARY) ? 9 : 7; break;
    }

    if (uf->uf_bsize == 7) {
	/* Assume 7-bit bytes means ASCII, unless user explicitly asks */
	/* for image mode: */
	if (flags & O_ITS_IMAGE) bits |= _DOIMG;
    } else {
	/* Assume other byte sizes mean image mode, unless user */
	/* explicitly says not to.  Turn on block mode and signal */
	/* handpacking in either case. */
	bits |= ((flags & O_ITS_NO_IMAGE) ? _DOBLK : (_DOBLK | _DOIMG));
	uf->uf_flgs |= UIOF_HANDPACK;
    }

    if ((flags & O_CONVERTED) ||
	(uf->uf_bsize == 7 && !(flags & (O_UNCONVERTED | O_BINARY))))
	uf->uf_flgs |= UIOF_CONVERTED;

    if (strlen(path) > FILENAME_SIZE)
	return ENAMETOOLONG;

    cvt_filename(path, realpath);

    if (SYSCALL3("sopen", bits, uf->uf_ch, &realpath_ptr))
	return ENOENT;

    uf->uf_type = _dvtype(uf->uf_ch);
    if (uf->uf_type == _DVUSR) {
	int uind;
	SYSCALL3_LOSE("usrvar", uf->uf_ch, SC_SIX("uind"), SC_VAL(&uind));
	uf->uf_dnum = SC_IMM(_JSNUM | uind);
    }

    return 0;			/* Won, return without error! */
}

/*
 *	Convert an ITS filename to make it acceptable to SOPEN.
 *	"/" becomes ";"
 *	"." becomes space when embedded in name components
 *	tab becomes space
 *	^Q inhibits the above
 */

static void cvt_filename(from, to)
    char *from, *to;
{
    char *ptr, *start;
    int c, len;

    strcpy(to, from);

    ptr = to-1;
    start = NULL;
    do {
	switch (c = *++ptr) {
	    case '/':
		*ptr = ';';
	    case ';':
	    case ':':
		start = NULL;
		break;

	    case '\t':
		*ptr = ' ';
	    case ' ':
	    case '\0':
		if (start)
		    while (--len > 0)
			switch (*++start) {
			    case '.': *start = ' '; break;
			    case '\021': ++start; break;
			}
		start = NULL;
		break;

	    case '\021':
		c = *++ptr;
	    default:
		if (start) len++;
		else { start = ptr; len = 0; }
		break;
	}
    } while (c);
}

#endif /* SYS_ITS */
#if 0	/* OLD STUFF for T20/10X, no longer used. */

static char *
tnxprot(uprot, str)
int uprot;
char *str;
{
    char *s = str;
    *s++ = ';';
    *s++ = 'P';
    f = modprot(mode >> 6);		/* user prots */
    *s++ = (f >> 3) + '0';
    *s++ = (f & 7) + '0';
    f = modprot(mode >> 3);		/* group prots */
    *s++ = (f >> 3) + '0';
    *s++ = (f & 7) + '0';
    f = modprot(mode);		/* other prots */
    *s++ = (f >> 3) + '0';
    *s++ = (f & 7) + '0';
    return str;
}

/* Unix -> T20/10X file protection conversion
*/
static int
modprot(mod)
int mod;
{
    int prot = 0;
    if (mod & 1) prot |= 012;		/* execute (and list) access */
    if (mod & 2) prot |= 027;		/* write and append access */
    if (mod & 4) prot |= 042;		/* read (and list) access */
    return prot;
}
/* --------------------------------------------------------------------- */
/*      jacket routine for GTJFN% to mung filenames into submission      */
/* --------------------------------------------------------------------- */

int _gtjfn(name, flags)
char *name;
{
    char dirpart[80], filpart[80], *dirptr, *filptr, *_dirst();
    int anydir, indir;			/* marker for dir part changed */

    anydir = 0;				/* dir part remains default */
    indir = 0;				/* assume not in directory part */
    dirpart[0] = 'D';			/* start off with directory part */
    dirpart[1] = 'S';			/* pointing to "DSK:" */
    dirpart[2] = 'K';
    dirpart[3] = ':';
    dirptr = &dirpart[3];		/* point to just before the end */
    filptr = &filpart[-1];		/* of dir and file parts */

    while (1) {
	switch (*name) {
	case '\0':				/* run out of chars? */
	    *++filptr = 0;			/* yes, null terminate */
	    filptr = &filpart[-1];		/* start at top again */
	    while (*++dirptr = *++filptr) ;	/* append to dir part */
	    return gtjfn_(dirpart, flags);	/* do low level lookup */

	case ':':
	    if (anydir) return -1;	/* already have dir, lose */
	    anydir = 1;			/* now we have one */
	    *++filptr = '\0';		/* terminate file part */
	    dirptr = &dirpart[-1];	/* start at top */
	    filptr = &filpart[-1];	/* top of filename part */
	    while (*++dirptr = *++filptr); /* copy across */
	    *dirptr = ':';		/* put the colon on */
	    filptr = &filpart[-1];	/* reinitialize file pointer */
	    break;

	case '[':
	case '<':
	    *++filptr = '<';		/* open bracket becomes angle */
	    indir = 1;			/* remember we're in dir part */
	    break;

	case '.':
	    if (indir < 0) *++filptr = '\026'; /* quote extra dots */
	    else if (indir == 0) indir = -1; /* only allow one in file */
	    *++filptr = '.';		/* add the dot */
	    break;

	case '>':
	case ']':
	    if (*dirptr != ':') return -1; /* already have dir, lose */
	    anydir = 1;			/* remember we have a dir and dev */
	    indir = 0;			/* no longer in dir part */
	    *++filptr = '\0';		/* terminate file part */
	    filptr = &filpart[-1];	/* top of filename part */
	    while (*++dirptr = *++filptr); /* copy across */
	    *dirptr = '>';		/* add the close bracket */
	    filptr = &filpart[-1];	/* reinitialize file pointer */
	    break;

	case '\026':			/* control-V? */
	case '\\':			/* or quoteing backslash? */
	    *++filptr = '\026';		/* yes, add it */
	    *++filptr = *++name;	/* and the next char */
	    break;

	case '/':			/* slash UNIX dir delimiter? */
	    indir = 0;			/* yes, no longer in dir part */
	    *(dirptr+1) = '\0';		/* yes, terminate directory part */
	    *++filptr = '\0';		/* and filename part */
	    switch (filpart[0]) {	/* check out first part of name */
	    case '.':			/* period? */
		switch (filpart[1]) {	/* yes, look at what follows */
		case '\0':		/* ./x ? */
		    name++;		/* yes, skip slash */
		    filptr = &filpart[-1]; /* start again in file part */
		    continue;		/* and ignore this dir part */

		default:
		    return -1;		/* .x/y loses */

		case '\026':		/* maybe ..? */
		    break;		/* yes, handle outside switch */
		}

		/* dir starts with .. - hack superdirectory */
		if (filpart[2] != '.' || filpart[3] != '\0') return -1;
		if (*dirptr != '>') {	/* do we have a real dir part? */
		    dirptr = _dirst(dirpart); /* no, fix it up */
		    if (dirptr == NULL) return -1; /* lost */
		}
		while (*--dirptr != '.' && *dirptr != ':') ; /* find delim */
		if (*dirptr == '.') {	/* found subdir start? */
		    *dirptr = '>';	/* turn into dir end */
		    name++;		/* skip over slash */
		    filptr = &filpart[-1]; /* forget .. */
		    continue;		/* on with the show */
		}
		/* no subdirs, move back to dev:<root-dir> */

	    case '\0':			/* no name, want root dir */
		if (*dirptr != ':') return -1; /* must have only dev */
		filptr = "<ROOT-DIRECTORY" - 1; /* point to dir part */
		break;

	    default:
		if (*dirptr != '>') {
		    dirptr = _dirst(dirpart); /* fix up filename */
		    if (dirptr == NULL) return -1; /* lost */
		}
		*dirptr = '.';		/* drop period over close bracket */
		filptr = &filpart[-1];	/* normal name, just use it */
	    }
	    while (*++dirptr = *++filptr) ; /* append name */
	    *dirptr = '>';		/* and close bracket */
	    filptr = &filpart[-1];	/* restart file part */
	    break;

	default:
	    *++filptr = *name;		/* normal char, add to file part */
	    break;
	}
	name++;				/* move on to next char */
    }
}
#endif	/* Commented-out stuff */

#endif /* T20+10X+T10+CSI+WAITS+ITS */