Source to mint/tosfs.c


Enter a symbol's name here to quickly find it.

/*
 * This file has been modified as part of the FreeMiNT project. See
 * the file Changes.MH for details and dates.
 */

/*
Copyright 1991,1992 Eric R. Smith.
Copyright 1992,1993,1994 Atari Corporation.
All rights reserved.
*/

/* a VERY simple tosfs.c 
 * this one is extremely brain-damaged, but will serve OK for a
 * skeleton in which to put a "real" tosfs.c
 */

#include "mint.h"

/* if NEWWAY is defined, tosfs uses the new dup_cookie/release_cookie
 * protocol to keep track of file cookies, instead of the old
 * method of "timing"
 */
#define NEWWAY
#if 0
#define COOKIE_DB(x) DEBUG(x)
#else
#define COOKIE_DB(x)
#endif

/* if RO_FASCISM is defined, the read/write modes are enforced. This is
 * a Good Thing, not fascist at all. Ask Allan Pratt why he chose
 * that name sometime.
 */
#define RO_FASCISM

/* temporary code for debugging Falcon media change bug */
#if 0
#define MEDIA_DB(x) DEBUG(x)
#else
#define MEDIA_DB(x)
#endif

/* search mask for anything OTHER THAN a volume label */
#define FILEORDIR 0x37

char tmpbuf[PATH_MAX+1];

static long	ARGS_ON_STACK tos_root	P_((int drv, fcookie *fc));
static long	ARGS_ON_STACK tos_lookup	P_((fcookie *dir, const char *name, fcookie *fc));
static long	ARGS_ON_STACK tos_getxattr	P_((fcookie *fc, XATTR *xattr));
static long	ARGS_ON_STACK tos_chattr	P_((fcookie *fc, int attrib));
static long	ARGS_ON_STACK tos_chown	P_((fcookie *fc, int uid, int gid));
static long	ARGS_ON_STACK tos_chmode	P_((fcookie *fc, unsigned mode));
static long	ARGS_ON_STACK tos_mkdir	P_((fcookie *dir, const char *name, unsigned mode));
static long	ARGS_ON_STACK tos_rmdir	P_((fcookie *dir, const char *name));
static long	ARGS_ON_STACK tos_remove	P_((fcookie *dir, const char *name));
static long	ARGS_ON_STACK tos_getname	P_((fcookie *root, fcookie *dir,
						    char *pathname, int size));
static long	ARGS_ON_STACK tos_rename	P_((fcookie *olddir, char *oldname,
				    fcookie *newdir, const char *newname));
static long	ARGS_ON_STACK tos_opendir	P_((DIR *dirh, int flags));
static long	ARGS_ON_STACK tos_readdir	P_((DIR *dirh, char *nm, int nmlen, fcookie *));
static long	ARGS_ON_STACK tos_rewinddir	P_((DIR *dirh));
static long	ARGS_ON_STACK tos_closedir	P_((DIR *dirh));
static long	ARGS_ON_STACK tos_pathconf	P_((fcookie *dir, int which));
static long	ARGS_ON_STACK tos_dfree	P_((fcookie *dir, long *buf));
static long	ARGS_ON_STACK tos_writelabel	P_((fcookie *dir, const char *name));
static long	ARGS_ON_STACK tos_readlabel	P_((fcookie *dir, char *name, int namelen));

static long	ARGS_ON_STACK tos_creat	P_((fcookie *dir, const char *name, unsigned mode,
				    int attrib, fcookie *fc));
static DEVDRV *	ARGS_ON_STACK tos_getdev	P_((fcookie *fc, long *devsp));
static long	ARGS_ON_STACK tos_open	P_((FILEPTR *f));
static long	ARGS_ON_STACK tos_write	P_((FILEPTR *f, const char *buf, long bytes));
static long	ARGS_ON_STACK tos_read	P_((FILEPTR *f, char *buf, long bytes));
static long	ARGS_ON_STACK tos_lseek	P_((FILEPTR *f, long where, int whence));
static long	ARGS_ON_STACK tos_ioctl	P_((FILEPTR *f, int mode, void *buf));
static long	ARGS_ON_STACK tos_datime	P_((FILEPTR *f, short *time, int rwflag));
static long	ARGS_ON_STACK tos_close	P_((FILEPTR *f, int pid));
static long	ARGS_ON_STACK tos_dskchng	P_((int drv));

#ifdef NEWWAY
static long	ARGS_ON_STACK tos_release P_((fcookie *fc));
static long	ARGS_ON_STACK tos_dupcookie P_((fcookie *dst, fcookie *src));
#endif

/* some routines from biosfs.c */
extern long	ARGS_ON_STACK null_select	P_((FILEPTR *f, long p, int mode));
extern void	ARGS_ON_STACK null_unselect	P_((FILEPTR *f, long p, int mode));

DEVDRV tos_device = {
	tos_open, tos_write, tos_read, tos_lseek, tos_ioctl, tos_datime,
	tos_close, null_select, null_unselect
};

FILESYS tos_filesys = {
	(FILESYS *)0,
	FS_KNOPARSE | FS_NOXBIT | FS_LONGPATH,
	tos_root,
	tos_lookup, tos_creat, tos_getdev, tos_getxattr,
	tos_chattr, tos_chown, tos_chmode,
	tos_mkdir, tos_rmdir, tos_remove, tos_getname, tos_rename,
	tos_opendir, tos_readdir, tos_rewinddir, tos_closedir,
	tos_pathconf, tos_dfree, tos_writelabel, tos_readlabel,
	nosymlink, noreadlink, nohardlink, nofscntl, tos_dskchng,
#ifdef NEWWAY
	tos_release, tos_dupcookie
#else
	0, 0
#endif
};

/* some utility functions and variables: see end of file */
static DTABUF 	*lastdta;	/* last DTA buffer we asked TOS about */
static DTABUF	foo;
static void do_setdta P_((DTABUF *dta));
static int executable_extension P_((char *));

/* this array keeps track of which drives have been changed */
/* a nonzero entry means that the corresponding drive has been changed,
 * but GEMDOS doesn't know it yet
 */
static char drvchanged[NUM_DRIVES];

/* force TOS to see a media change */
static void force_mediach P_((int drv));
static long ARGS_ON_STACK Newgetbpb P_((int));
static long ARGS_ON_STACK Newmediach P_((int));
static long ARGS_ON_STACK Newrwabs P_((int, void *, int, int, int, long));

#ifdef NEWWAY
#define NUM_INDICES 64
#else
#define NUM_INDICES 128
#define MIN_AGE 8
#endif

struct tindex {
	char *name;		/* full path name */
	FILEPTR *open;		/* fileptrs for this file; OR
				 * count of number of open directories
				 */
	LOCK *locks;		/* locks on this file */
/* file status */
	long  size;
	short time;
	short date;
	short attr;
	short valid;		/* 1 if the above status is still valid */
#ifdef NEWWAY
	short links;		/* how many times index is in use */
#else
	short stamp;		/* age of this index, for garbage collection */
#endif
} gl_ti[NUM_INDICES];

/* temporary index for files found by readdir */
static struct tindex tmpindex;
static char tmpiname[PATH_MAX];

static struct tindex *tstrindex P_((char *s));
static int tfullpath P_((char *result, struct tindex *base, const char *name));
static struct tindex *garbage_collect P_((void));

#ifndef NEWWAY
static short tclock;		/* #calls to tfullpath since last garbage
				   collection */
#endif


#define ROOTPERMS 32
#ifdef ROOTPERMS
/* jr: save access permissions, owner and group for root directories.
   ROOTPERMS is the number of supported drives; 'mode' has ROOTPERMSET
   if it has been set  */

struct tosrootperm {
	ushort uid, gid, mode;
} root_perms[ROOTPERMS];

#define ROOTPERMSET	0x8000
#endif

/* jr: external table, set up by getbpb in bios.c */
extern unsigned short clsizb[];

/*#define INODE_PER_CRC */

#ifdef INODE_PER_CRC

/* jr: use crc algorithm described in P1003.2, D11.2 with crctab as in
   GNU-cksum. Slightly modified to return 0 for the root (instead of ~0) */

static unsigned long crctab[] = { /* CRC polynomial 0xedb88320 */
0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L,
0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L,
0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L,
0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L,
0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L,
0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL,
0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL,
0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L,
0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L,
0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L,
0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL,
0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L,
0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL,
0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L,
0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L,
0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L,
0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL,
0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L,
0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL,
0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL,
0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L,
0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L,
0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L,
0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL,
0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL,
0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL
};

#define UPDC32(octet, crc) (((crc) << 8) ^ crctab[((crc) >> 24) ^ (octet)])

static unsigned long
filename_crc (const char *filename)
{
	unsigned long s = 0;
	unsigned int n = 0;
	
	/* skip x: */
	filename += 2;
	
	while (*filename) {
		s = UPDC32 (*filename++, s);
		n++;
	}

	while (n != 0) {
		s = UPDC32 (n & 0377, s);
		n >>= 8;
	}
	
	return s;
}

#endif


/* some extra flags for the attr field */

/*
 * is a string the name of a file with executable extension?
 */
#define FA_EXEC 0x4000
/*
 * should the file be deleted when it is closed?
 */
#define FA_DELETE 0x2000

/*
 * NOTE: call executable_extension only on a DTA name returned from
 * Fsfirst(), not on an arbitrary path, for two reasons: (1) it
 * expects only upper case, and (2) it looks only for the 1st extension,
 * so a folder with a '.' in its name would confuse it.
 */

static int
executable_extension(s)
	char *s;
{
	while (*s && *s != '.') s++;
	if (!*s) return 0;
	s++;
	if (s[0] == 'T') {
		return (s[1] == 'T' && s[2] == 'P') ||
		       (s[1] == 'O' && s[2] == 'S');
	}
	if (s[0] == 'P')
		return s[1] == 'R' && s[2] == 'G';
	if (s[0] == 'A')
		return s[1] == 'P' && s[2] == 'P';
	if (s[0] == 'G')
		return s[1] == 'T' && s[2] == 'P';
	return 0;
}

/*
 * Look in the table of tos indices to see if an index corresponding
 * to this file name already exists. If so, mark it as being used
 * and return it. If not, find an empty slot and make an index for
 * this string. If no empty slots exist, garbage collect and
 * try again.
 *
 * This routine is pretty dumb; we really should use a hash table
 * of some sort
 */

static struct tindex *tstrindex(s)
	char *s;
{
	int i;
	char *r;
	struct tindex *t, *free = 0;

	assert(s != 0);
	t = gl_ti;
	for (i = 0; i < NUM_INDICES; i++, t++) {
		if (t->name && !stricmp(t->name, s)) {
#ifndef NEWWAY
			t->stamp = tclock;	/* update use time */
#endif
			return t;
		}
		else if (!t->name && !free)
			free = t;
	}
	if (!free) {
		free = garbage_collect();
	}
#ifdef NEWWAY
	if (!free) {
		FORCE("tosfs: all slots in use!!");
		FORCE("Links\tName");
		t = gl_ti;
		for (i = 0; i < NUM_INDICES; i++,t++) {
			FORCE("%d\t%s", t->links, t->name);
		}
		FATAL("tosfs: unable to get a file name index");
	}
#else
	if (!free) {
		FATAL("tosfs: unable to get a file name index");
	}
#endif
	r = kmalloc((long)strlen(s)+1);
	if (!r) {
		FATAL("tosfs: unable to allocate space for a file name");
	}
	strcpy(r, s);
	free->name = r;
#ifdef NEWWAY
	free->links = 0;
#else
	free->stamp = tclock;
#endif
	free->open = 0;
	free->locks = 0;

/* check to see if this file was recently returned by opendir() */
#ifndef NEWWAY
	if (tmpindex.valid && tclock - tmpindex.stamp < MIN_AGE &&
	    !stricmp(free->name, tmpindex.name)) {
		free->size = tmpindex.size;
		free->time = tmpindex.time;
		free->date = tmpindex.date;
		free->attr = tmpindex.attr;
		free->valid = 1;
		tmpindex.valid = 0;
	} else
#endif
	{
		free->valid = 0;
		free->attr = 0;
	}
	return free;
}

/*
 * garbage collection routine: for any TOS index older than MIN_AGE,
 * check through all current processes to see if it's in use. If
 * not, free the corresponding string.
 * Returns: a pointer to a newly freed index, or NULL.
 */

/* it's unlikely that the kernel would need to hold onto a file cookie
   for longer than this many calls to tstrindex() without first
   saving the cookie in a directory or file pointer
 */

static struct tindex *
garbage_collect()
{
	struct tindex *free, *t;
	int i;
#ifndef NEWWAY
	fcookie *fc, *gc;
	PROC *p;
	int j;
	int age;
#endif

	free = 0;
	t = gl_ti;
	for (i = 0; i < NUM_INDICES; i++,t++) {
		if (!t->name) continue;
#ifdef NEWWAY
		if (t->links == 0) {
			kfree(t->name);
			t->name = 0;
			if (!free) free = t;
		}
#else
		age = tclock - t->stamp;
		t->stamp = 0;
		assert(age >= 0);
		if (age > MIN_AGE) {
		/* see if any process is using this index */
			if (t->open)
				goto found_index;
			for (p = proclist; p; p = p->gl_next) {
				fc = p->curdir;
				gc = p->root;
				for (j = 0; j < NUM_DRIVES; j++,fc++,gc++) {
					if (( fc->fs == &tos_filesys &&
					      fc->index == (long)t ) ||
					    ( gc->fs == &tos_filesys &&
					      gc->index == (long)t ) )
						goto found_index;
				}
			}
		/* here, we couldn't find the index in use by any proc. */
			kfree(t->name);
			t->name = 0;
			if (!free)
				free = t;
		found_index:
			;
		} else {
	/* make sure that future garbage collections might look at this file */
			t->stamp = -age;
		}
#endif
	}

#ifndef NEWWAY
	tclock = 0;	/* reset the clock */
	tmpindex.valid = 0; /* expire the temporary Fsfirst buffer */
#endif
	return free;
}

#define DIRSEP(c) ((c) == '\\')

static int
tfullpath(result, basei, path)
	char *result;
	struct tindex *basei;
	const char *path;
{
#define TNMTEMP 32
	char *n, name[TNMTEMP+1];
	int namelen, pathlen;
	char *base = basei->name;
	int r = 0;

#ifndef NEWWAY
	basei->stamp = ++tclock;
	if (tclock > 10000) {
	/* garbage collect every so often whether we need it or not */
		(void)garbage_collect();
	}
#endif
	if (!*path) {
		strncpy(result, base, PATH_MAX-1);
		return r;
	}

	strncpy(result, base, PATH_MAX-1);

	pathlen = strlen(result);

/* now path is relative to what's currently in "result" */

	while(*path) {
/* get next name in path */
		n = name; namelen = 0;
		while (*path && !DIRSEP(*path)) {
/* BUG: we really should to the translation to DOS 8.3
 * format *here*, so that really long names are truncated
 * correctly.
 */
			if (namelen < TNMTEMP) {
				*n++ = toupper(*path); path++; namelen++;
			}
			else
				path++;
		}
		*n = 0;
		while (DIRSEP(*path)) path++;
/* check for "." and ".." */
		if (!strcmp(name, ".")) continue;
		if (!strcmp(name, "..")) {
			n = strrchr(result, '\\');
			if (n) {
				*n = 0;
				pathlen = (int)(n - result);
			}
			else r = EMOUNT;
			continue;
		}
		if (pathlen + namelen < PATH_MAX - 1) {
			strcat(result, "\\");
			pathlen++;

	/* make sure the name is restricted to DOS 8.3 format */
			for (base = result; *base; base++)
				;
			n = name;
			namelen = 0;
			while (*n && *n != '.' && namelen++ < 8) {
				*base++ = *n++;
				pathlen++;
			}
			while (*n && *n != '.') n++;
			if (*n == '.' && *(n+1) != 0) {
				*base++ = *n++;
				pathlen++;
				namelen = 0;
				while (*n && namelen++ < 3) {
					*base++ = *n++;
					pathlen++;
				}
			}
			*base = 0;
		}
	}
	return r;
}

static long ARGS_ON_STACK 
tos_root(drv, fc)
	int drv;
	fcookie *fc;
{
	struct tindex *ti;

	ksprintf(tmpbuf, "%c:", drv+'A');
	fc->fs = &tos_filesys;
	fc->dev = drv;
	ti = tstrindex(tmpbuf);
	ti->size = ti->date = ti->time = 0;
	ti->attr = FA_DIR;
	ti->valid = 1;
	fc->index = (long)ti;

/* if the drive has changed, make sure GEMDOS knows it! */
	if (drvchanged[drv]) {
		force_mediach(drv);
	}
#ifdef NEWWAY
	ti->links++;
#endif
	return 0;
}

static long ARGS_ON_STACK 
tos_lookup(dir, name, fc)
	fcookie *dir;
	const char *name;
	fcookie *fc;
{
	long r;
	struct tindex *ti = (struct tindex *)dir->index;

	r = tfullpath(tmpbuf, ti, name);

/* if the name is empty or otherwise trivial, just return the directory */
	if (!strcmp(ti->name, tmpbuf)) {
		*fc = *dir;
#ifdef NEWWAY
		ti->links++;
		COOKIE_DB(("tos_lookup: %s now has %d links", ti->name, ti->links));
#endif 
		return r;
	}

/* is there already an index for this file?? If so, is it up to date?? */
	ti = tstrindex(tmpbuf);
	if (!ti->valid) {
		if (tmpbuf[1] == ':' && tmpbuf[2] == 0) {
			/* a root directory -- lookup always succeeds */
			foo.dta_size = 0;
			foo.dta_date = foo.dta_time = 0;
			foo.dta_attrib = FA_DIR;
			foo.dta_name[0] = 0;
		} else {
			do_setdta(&foo);
			r = Fsfirst(tmpbuf, FILEORDIR);
			if (r) {
DEBUG(("tos_lookup: Fsfirst(%s) returned %ld", tmpbuf, r));
				return r;
			}
		}
		ti->size = foo.dta_size;
		ti->date = foo.dta_date;
		ti->time = foo.dta_time;
		ti->attr = foo.dta_attrib | (ti->attr & FA_DELETE);
		if (executable_extension(foo.dta_name))
			ti->attr |= FA_EXEC;
		ti->valid = 1;
	}
	fc->fs = &tos_filesys;
	fc->index = (long)ti;
	fc->dev = dir->dev;
#ifdef NEWWAY
	ti->links++;
	COOKIE_DB(("tos_lookup: %s now has %d links", ti->name, ti->links));
#endif
	return r;
}

static long ARGS_ON_STACK
tos_getxattr(fc, xattr)
	fcookie *fc;
	XATTR *xattr;
{
	struct tindex *ti = (struct tindex *)fc->index;
	long r;
#ifndef INODE_PER_CRC
	static long junkindex = 0;
#endif
#ifdef ROOTPERMS
	struct tosrootperm *tp = NULL;
	
	if (fc->dev < ROOTPERMS) tp = &root_perms[fc->dev];
#endif

#ifdef INODE_PER_CRC
	xattr->index = filename_crc (ti->name);
#else
	xattr->index = junkindex++;
#endif

	xattr->dev = fc->dev;
	xattr->rdev = fc->dev;
	xattr->nlink = 1;

#ifdef ROOTPERMS
	if (tp) {
		xattr->uid = tp->uid;
		xattr->gid = tp->gid;
	} else
#endif
	xattr->uid = xattr->gid = 0;

#ifndef NEWWAY
	ti->stamp = ++tclock;
#endif

	if (!ti->valid) {
		do_setdta(&foo);
		if (ti->name[2] == 0) {		/* a root directory */
/* actually, this can also happen if a program tries to open a file
 * with an empty name... so we should fail gracefully
 */
			ti->attr = FA_DIR;
			ti->size = 0;
			ti->date = ti->time = 0;
			goto around;
		}
	
		r = Fsfirst(ti->name, FILEORDIR);
		if (r) {
			DEBUG(("tosfs: search error %ld on [%s]", r, ti->name));
			return r;
		}
		ti->size = foo.dta_size;
		ti->date = foo.dta_date;
		ti->time = foo.dta_time;
		ti->attr = foo.dta_attrib | (ti->attr & FA_DELETE);
		if (executable_extension(foo.dta_name))
			ti->attr |= FA_EXEC;
around:
		ti->valid = 1;
	}
	xattr->size = ti->size;

	/* jr: if cluster size unknown, do a getbpb once */
	if (fc->dev < 32 && ! clsizb[fc->dev])
		getbpb (fc->dev);

	xattr->blksize = 1024;
	if (fc->dev < 32 && clsizb[fc->dev])
		xattr->blksize = clsizb[fc->dev];
	
	xattr->nblocks = (xattr->size + xattr->blksize - 1) / xattr->blksize;
	if (!xattr->nblocks && (ti->attr & FA_DIR))
		xattr->nblocks = 1;	/* no dir takes 0 blocks... */
	xattr->mdate = xattr->cdate = xattr->adate = ti->date;
	xattr->mtime = xattr->ctime = xattr->atime = ti->time;
	xattr->mode = (ti->attr & FA_DIR) ? (S_IFDIR | DEFAULT_DIRMODE) :
			 (S_IFREG | DEFAULT_MODE);

#ifdef ROOTPERMS
	/* when root permissions are set, use them. For regular files,
	unmask x bits */

	if (tp && (tp->mode & ROOTPERMSET)) {
		xattr->mode &= ~DEFAULT_DIRMODE;
		xattr->mode |= (tp->mode & DEFAULT_DIRMODE);

		if (!(ti->attr & FA_DIR) && !(ti->attr & FA_EXEC))
			xattr->mode &= ~(S_IXUSR|S_IXGRP|S_IXOTH);
	} else
#endif
	{
		if (ti->attr & FA_EXEC) {
			xattr->mode |= (S_IXUSR|S_IXGRP);
		}

		/* TOS files have permissions rwxrwx--- */
		xattr->mode &= ~(S_IROTH|S_IWOTH|S_IXOTH);
	}

	if (ti->attr & FA_RDONLY) {
		xattr->mode &= ~(S_IWUSR|S_IWGRP|S_IWOTH);
	}

	xattr->attr = ti->attr & 0xff;
	return 0;
}

static long ARGS_ON_STACK
tos_chattr(fc, attrib)
	fcookie *fc;
	int attrib;
{
	struct tindex *ti = (struct tindex *)fc->index;

	if (ti->attr & FA_DIR) {
		DEBUG(("error: attempt to change attributes of a directory"));
		return EACCDN;
	}
	ti->valid = 0;
	(void)tfullpath(tmpbuf, ti, "");
	return Fattrib(tmpbuf, 1, attrib);
}

static long ARGS_ON_STACK 
tos_chown(dir, uid, gid)
	fcookie *dir;
	int uid, gid;
{
#ifdef ROOTPERMS
	struct tindex *ti = (struct tindex *)dir->index;
	struct tosrootperm *tp ;

	if (dir->dev < ROOTPERMS)
	{
		tp = &root_perms[dir->dev];
		if (ti->name[2] == '\0')	/* root? */
		{
			tp->uid = uid;
			tp->gid = gid;
			return 0;	
		}
		return (tp->uid==uid && tp->gid==gid) ? 0 : EINVFN ;
	}
	
	return EINVFN;
	
#else
	UNUSED(dir); UNUSED(uid); UNUSED(gid);
	return EINVFN;
#endif
}

static long ARGS_ON_STACK 
tos_chmode(fc, mode)
	fcookie *fc;
	unsigned mode;
{
	int oldattr, newattr;
	long r;
	struct tindex *ti = (struct tindex *)fc->index;

#ifdef ROOTPERMS
	if (fc->dev < ROOTPERMS) {
		if (ti->name[2] == '\0') {
			/* root? */
			root_perms[fc->dev].mode = ROOTPERMSET | mode;
			return 0;
		}
	}
#endif

	oldattr = Fattrib(ti->name, 0, 0);
	if (oldattr < 0)
		return oldattr;

	ti->valid = 0;

	if (!(mode & S_IWUSR))
		newattr = oldattr | FA_RDONLY;
	else
		newattr = oldattr & ~FA_RDONLY;
	if (newattr != oldattr)
		r = Fattrib(ti->name, 1, newattr);
	else
		r = 0;
	return (r < 0) ? r : 0;
}

static long ARGS_ON_STACK 
tos_mkdir(dir, name, mode)
	fcookie *dir;
	const char *name;
	unsigned mode;		/* ignored under TOS */
{
	UNUSED(mode);

	(void)tfullpath(tmpbuf, (struct tindex *)dir->index, name);
	tmpindex.valid = 0;

	return Dcreate(tmpbuf);
}

static long ARGS_ON_STACK 
tos_rmdir(dir, name)
	fcookie *dir;
	const char *name;
{
	struct tindex *ti;

	(void)tfullpath(tmpbuf, (struct tindex *)dir->index, name);
	ti = tstrindex(tmpbuf);
	ti->valid = 0;

	return Ddelete(tmpbuf);
}

static long ARGS_ON_STACK 
tos_remove(dir, name)
	fcookie *dir;
	const char *name;
{
	struct tindex *ti;

	(void)tfullpath(tmpbuf, (struct tindex *)dir->index, name);

	ti = tstrindex(tmpbuf);
	if (ti->open) {
		DEBUG(("tos_remove: file is open, will be deleted later"));
		if (ti->attr & FA_RDONLY)
			return EACCDN;
		ti->attr |= FA_DELETE;
		return 0;
	}
	ti->valid = 0;
	return Fdelete(tmpbuf);
}

static long ARGS_ON_STACK 
tos_getname(root, dir, pathname, size)
	fcookie *root, *dir;
	char *pathname;
	int size;
{
	char *rootnam = ((struct tindex *)root->index)->name;
	char *dirnam = ((struct tindex *)dir->index)->name;
	int i;

	i = strlen(rootnam);
	if (strlen(dirnam) < i) {
		DEBUG(("tos_getname: root is longer than path"));
		return EINTRN;
	}
	if (strlen(dirnam+i) < size) {
		strcpy(pathname, dirnam + i);
/*
 * BUG: must be a better way to decide upper/lower case
 */
		if (curproc->domain == DOM_MINT)
			strlwr(pathname);
		return 0;
	} else {
		DEBUG(("tosfs: name too long"));
		return ERANGE;
	}
}

static long ARGS_ON_STACK 
tos_rename(olddir, oldname, newdir, newname)
	fcookie *olddir;
	char *oldname;
	fcookie *newdir;
	const char *newname;
{
	char newbuf[128];
	struct tindex *ti;
	long r;

	(void)tfullpath(tmpbuf, (struct tindex *)olddir->index, oldname);
	(void)tfullpath(newbuf, (struct tindex *)newdir->index, newname);
	r = Frename(0, tmpbuf, newbuf);
	if (r == 0) {
		ti = tstrindex(tmpbuf);
		kfree(ti->name);
		ti->name = kmalloc((long)strlen(newbuf)+1);
		if (!ti->name) {
			FATAL("tosfs: unable to allocate space for a name");
		}
		strcpy(ti->name, newbuf);
		ti->valid = 0;
	}
	return r;
}

#define DIR_FLAG(x)	(x->fsstuff[0])
#define STARTSEARCH	0	/* opendir() was just called */
#define INSEARCH	1	/* readdir() has been called at least once */
#define NMFILE		2	/* no more files to read */

#define DIR_DTA(x)	((DTABUF *)(x->fsstuff + 2))
#define DIR_NAME(x)	(x->fsstuff + 32)

/*
 * The directory functions are a bit tricky. What we do is have
 * opendir() do Fsfirst; the first readdir() picks up this name,
 * subsequent readdir()'s have to do Fsnext
 */

static long ARGS_ON_STACK 
tos_opendir(dirh, flags)
	DIR *dirh;
	int flags;
{
	long r;
	struct tindex *t = (struct tindex *)dirh->fc.index;

	UNUSED(flags);

	(void)tfullpath(tmpbuf, t, "*.*");

	do_setdta(DIR_DTA(dirh));

	r = Fsfirst(tmpbuf, FILEORDIR);

	if (r == 0) {
		t->open++;
		DIR_FLAG(dirh) = STARTSEARCH;
		return 0;
	} else if (r == EFILNF) {
		t->open++;
		DIR_FLAG(dirh) = NMFILE;
		return 0;
	}
 	return r;
}

static long ARGS_ON_STACK 
tos_readdir(dirh, name, namelen, fc)
	DIR *dirh;
	char *name;
	int namelen;
	fcookie *fc;
{
	static long index = 0;
	long ret;
	int giveindex = dirh->flags == 0;
	struct tindex *ti;
	DTABUF *dta = DIR_DTA(dirh);

again:
	if (DIR_FLAG(dirh) == NMFILE)
		return ENMFIL;

	if (DIR_FLAG(dirh) == STARTSEARCH) {
		DIR_FLAG(dirh) = INSEARCH;
	} else {
		assert(DIR_FLAG(dirh) == INSEARCH);
		do_setdta(dta);
		ret = Fsnext();
		if (ret) {
			DIR_FLAG(dirh) = NMFILE;
			return ret;
		}
	}

/* don't return volume labels from readdir */
	if (dta->dta_attrib == FA_LABEL) goto again;

	fc->fs = &tos_filesys;
	fc->dev = dirh->fc.dev;

	(void)tfullpath(tmpiname, (struct tindex *)dirh->fc.index, DIR_NAME(dirh));

	ti = &tmpindex;
	ti->name = tmpiname;
	ti->valid = 1;
	ti->size = dta->dta_size;
	ti->date = dta->dta_date;
	ti->time = dta->dta_time;
	ti->attr = dta->dta_attrib;
#ifndef NEWWAY
	ti->stamp = tclock;
#endif
	if (executable_extension(dta->dta_name))
		ti->attr |= FA_EXEC;
	fc->index = (long)ti;

	if (giveindex) {
		namelen -= (int) sizeof(long);
		if (namelen <= 0) return ERANGE;
		*((long *)name) = index++;
		name += sizeof(long);
	}
	strncpy(name, DIR_NAME(dirh), namelen-1);
	name[namelen-1] = 0;

/* BUG: we really should do the "strlwr" operation only
 * for Dreaddir (i.e. if giveindex == 0) but
 * unfortunately some old programs rely on the behaviour
 * below
 */
	if (curproc->domain == DOM_MINT) {
		strlwr(name);
	}
	if (strlen(DIR_NAME(dirh)) >= namelen)
		return ENAMETOOLONG;
#ifdef NEWWAY
	ti->links++;
	COOKIE_DB(("tos_readdir: %s now has %d links", ti->name, ti->links));
#endif
	return 0;
}

static long ARGS_ON_STACK 
tos_rewinddir(dirh)
	DIR *dirh;
{
	struct tindex *ti = (struct tindex *)dirh->fc.index;
	long r;

	(void)tfullpath(tmpbuf, ti, "*.*");
	do_setdta(DIR_DTA(dirh));
	r = Fsfirst(tmpbuf, FILEORDIR);
	if (r == 0) {
		DIR_FLAG(dirh) = STARTSEARCH;
	} else {
		DIR_FLAG(dirh) = NMFILE;
	}
	return r;
}

static long ARGS_ON_STACK 
tos_closedir(dirh)
	DIR *dirh;
{
	struct tindex *t = (struct tindex *)dirh->fc.index;

	if (t->open == 0) {
		FATAL("t->open == 0: directory == %s", t->name);
	}
	--t->open;
	DIR_FLAG(dirh) = NMFILE;
	return 0;
}

static long ARGS_ON_STACK 
tos_pathconf(dir, which)
	fcookie *dir;
	int which;
{
	UNUSED(dir);

	switch(which) {
	case -1:
		return DP_MAXREQ;
	case DP_IOPEN:
		return 60;	/* we can only keep about this many open */
	case DP_MAXLINKS:
		 return 1;	/* no hard links */
	case DP_PATHMAX:
		return PATH_MAX;
	case DP_NAMEMAX:
		return 8+3+1;
	case DP_ATOMIC:
		return 512;	/* we can write at least a sector atomically */
	case DP_TRUNC:
		return DP_DOSTRUNC;	/* DOS style file names */
	case DP_CASE:
		return DP_CASECONV;	/* names converted to upper case */
	case DP_MODEATTR:
		return FA_RDONLY|FA_HIDDEN|FA_SYSTEM|FA_LABEL|FA_CHANGED|
#ifdef ROOTPERMS
				(0666L << 8)|
#endif
				DP_FT_DIR|DP_FT_REG;
	case DP_XATTRFIELDS:
		return DP_DEV|DP_NLINK|DP_BLKSIZE|DP_SIZE|DP_NBLOCKS|DP_MTIME;
	default:
		return EINVFN;
	}
}

long ARGS_ON_STACK 
tos_dfree(dir, buf)
	fcookie *dir;
	long *buf;
{
	return Dfree(buf, (dir->dev)+1);
}

/*
 * writelabel: creates a volume label
 * readlabel: reads a volume label
 * both of these are only guaranteed to work in the root directory
 */

/*
 * BUG: this should first delete any old labels, so that it will
 * work with TOS <1.4
 */

long ARGS_ON_STACK 
tos_writelabel(dir, name)
	fcookie *dir;
	const char *name;
{
	long r;
	struct tindex *ti;

	(void)tfullpath(tmpbuf, (struct tindex *)dir->index, name);
	r = Fcreate(tmpbuf, FA_LABEL);
	if (r < 0) return r;
	(void)Fclose((int)r);
	ti = tstrindex(tmpbuf);
	ti->valid = 0;
	return 0;
}

long ARGS_ON_STACK 
tos_readlabel(dir, name, namelen)
	fcookie *dir;
	char *name;
	int namelen;
{
	long r;
	struct tindex *ti = (struct tindex *)dir->index;

	if (ti->name[2] != 0)		/* not a root directory? */
		return EFILNF;

	(void)tfullpath(tmpbuf, ti, "*.*");
	do_setdta(&foo);
	r = Fsfirst(tmpbuf, FA_LABEL);
	if (r)
		return r;
	strncpy(name, foo.dta_name, namelen-1);
	return (strlen(foo.dta_name) < namelen) ? 0 : ENAMETOOLONG;
}

/*
 * TOS creat: this doesn't actually create the file, rather it
 * sets up a (fake) index for the file that will be used by
 * the later tos_open call.
 */

static long ARGS_ON_STACK 
tos_creat(dir, name, mode, attrib, fc)
	fcookie *dir;
	const char *name;
	unsigned mode;
	int attrib;
	fcookie *fc;
{
	struct tindex *ti;
	UNUSED(mode);

	(void)tfullpath(tmpbuf, (struct tindex *)dir->index, name);

	ti = tstrindex(tmpbuf);
	ti->size = 0;
	ti->date = datestamp;
	ti->time = timestamp;
	ti->attr = attrib;
	ti->valid = 1;

	fc->fs = &tos_filesys;
	fc->index = (long)ti;
	fc->dev = dir->dev;
#ifdef NEWWAY
	ti->links++;
	COOKIE_DB(("tos_creat: %s now has %d links", ti->name, ti->links));
#endif
	return 0;
}
	
/*
 * TOS device driver
 */

static DEVDRV * ARGS_ON_STACK 
tos_getdev(fc, devsp)
	fcookie *fc;
	long *devsp;
{
	UNUSED(fc); UNUSED(devsp);
	return &tos_device;
}

static long ARGS_ON_STACK 
tos_open(f)
	FILEPTR *f;
{
	struct tindex *ti;
	int mode = f->flags;
	int tosmode;
	long r;
	extern int flk;		/* in main.c, set if _FLK cookie already present */

	ti = (struct tindex *)(f->fc.index);
	assert(ti != 0);

#ifndef NEWWAY
	ti->stamp = ++tclock;
#endif
	ti->valid = 0;

#ifndef RO_FASCISM
/* TEMPORARY HACK: change all modes to O_RDWR for files opened in
 * compatibility sharing mode. This is silly, but
 * allows broken TOS programs that write to read-only handles to continue
 * to work (it also helps file sharing, by making the realistic assumption
 * that any open TOS file can be written to). Eventually,
 * this should be tuneable by the user somehow.
 * ALSO: change O_COMPAT opens into O_DENYNONE; again, this may be temporary.
 */
	if ( (mode & O_SHMODE) == O_COMPAT ) {
		f->flags = (mode & ~(O_RWMODE|O_SHMODE)) | O_RDWR | O_DENYNONE;
	}
#endif

/* check to see that nobody has opened this file already in an
 * incompatible mode
 */
	if (denyshare(ti->open, f)) {
		TRACE(("tos_open: file sharing denied"));
		return EACCDN;
	}

/*
 * now open the file; if O_TRUNC was specified, actually
 * create the file anew.
 * BUG: O_TRUNC without O_CREAT doesn't work right. The kernel doesn't
 * use this mode, anyways
 */

	if (mode & O_TRUNC) {
		if (ti->open) {
			DEBUG(("tos_open: attempt to truncate an open file"));
			return EACCDN;
		}
		r = Fcreate(ti->name, ti->attr);
	} else {
		if (flk)
			tosmode = mode & (O_RWMODE|O_SHMODE);
		else
			tosmode = (mode & O_RWMODE);
		if (tosmode == O_EXEC) tosmode = O_RDONLY;

		r = Fopen(ti->name, tosmode );
		if (r == EFILNF && (mode & O_CREAT))
			r = Fcreate(ti->name, ti->attr);
	}

	if (r < 0) {
/* get rid of the index for the file, since it doesn't exist */
		kfree(ti->name);
		ti->name = 0;
		ti->valid = 0;
		return r;
	}

	f->devinfo = r;

	f->next = ti->open;
	ti->open = f;
	return 0;
}

static long ARGS_ON_STACK 
tos_write(f, buf, bytes)
	FILEPTR *f; const char *buf; long bytes;
{
	struct tindex *ti = (struct tindex *)f->fc.index;

	ti->valid = 0;
	return Fwrite((int)f->devinfo, bytes, buf);
}

static long ARGS_ON_STACK 
tos_read(f, buf, bytes)
	FILEPTR *f; char *buf; long bytes;
{
	return Fread((int)f->devinfo, bytes, buf);
}

static long ARGS_ON_STACK 
tos_lseek(f, where, whence)
	FILEPTR *f; long where; int whence;
{
	long r;

	r = Fseek(where, (int)f->devinfo, whence);
	return r;
}

static long ARGS_ON_STACK 
tos_ioctl(f, mode, buf)
	FILEPTR *f; int mode; void *buf;
{
	LOCK t, *lck, **old;
	struct flock *fl;
	long r;
	struct tindex *ti;
	extern int flk;		/* set in main.c if _FLK already installed */

	switch(mode) {
	case FIONREAD:
		r = Fseek (0L, (int) f->devinfo, 1);
		if (r < 0) return r;
		*(long *) buf = Fseek (0L, (int) f->devinfo, 2) - r;
		(void) Fseek (r, (int) f->devinfo, 0);
		return 0;
	case FIONWRITE:
		*((long *)buf) = 1;
		return 0;
	case FIOEXCEPT:
		*((long *)buf) = 0;
		return 0;
	case F_SETLK:
	case F_SETLKW:
	case F_GETLK:
		fl = ((struct flock *)buf);
		t.l = *fl;
		switch(t.l.l_whence) {
		case 0:
			break;
		case 1:		/* SEEK_CUR */
			r = Fseek(0L, (int)f->devinfo, 1);
			t.l.l_start += r;
			break;
		case 2:
			r = Fseek(0L, (int)f->devinfo, 1);
			t.l.l_start = Fseek(t.l.l_start, (int)f->devinfo, 2);
			(void)Fseek(r, (int)f->devinfo, 0);
			break;
		default:
			DEBUG(("Invalid value for l_whence"));
			return EINVFN;
		}
/* BUG: can't lock a file starting at >2gigabytes from the beginning */
		if (t.l.l_start < 0) t.l.l_start = 0;
		t.l.l_whence = 0;
		ti = (struct tindex *)f->fc.index;

		if (mode == F_GETLK) {
			lck = denylock(ti->locks, &t);
			if (lck)
				*fl = lck->l;
			else
				fl->l_type = F_UNLCK;
			return 0;
		}

		if (t.l.l_type == F_UNLCK) {
		/* try to find the lock */
			old = &ti->locks;
			lck = *old;
			while (lck) {
				if (lck->l.l_pid == curproc->pid &&
				    lck->l.l_start == t.l.l_start &&
				    lck->l.l_len == t.l.l_len) {
		/* found it -- remove the lock */
					*old = lck->next;
					TRACE(("tosfs: unlocked %s: %ld + %ld",
						ti->name, t.l.l_start, t.l.l_len));
					if (flk)
					    (void)Flock((int)f->devinfo, 1,
							t.l.l_start, t.l.l_len);
				/* wake up anyone waiting on the lock */
					wake(IO_Q, (long)lck);
					kfree(lck);
					break;
				}
				old = &lck->next;
				lck = lck->next;
			}
			return lck ?  0 : ENSLOCK;
		}
		TRACE(("tosfs: lock %s: %ld + %ld", ti->name,
			t.l.l_start, t.l.l_len));
		do {
		/* see if there's a conflicting lock */
			while ((lck = denylock(ti->locks, &t)) != 0) {
				DEBUG(("tosfs: lock conflicts with one held by %d",
					lck->l.l_pid));
				if (mode == F_SETLKW) {
					sleep(IO_Q, (long)lck);		/* sleep a while */
				}
				else
					return ELOCKED;
			}
		/* if not, add this lock to the list */
			lck = kmalloc(SIZEOF(LOCK));
			if (!lck) return ENSMEM;
		/* see if other _FLK code might object */
			if (flk) {
				r = Flock((int)f->devinfo, 0, t.l.l_start, t.l.l_len);
				if (r) {
					kfree(lck);
					if (mode == F_SETLKW && r == ELOCKED) {
						yield();
						lck = NULL;
					}
					else
						return r;
				}
			}
		} while (!lck);
		lck->l = t.l;
		lck->l.l_pid = curproc->pid;
		lck->next = ti->locks;
		ti->locks = lck;
	/* mark the file as being locked */
		f->flags |= O_LOCK;
		return 0;
	}
	return EINVFN;
}

static long ARGS_ON_STACK 
tos_datime(f, timeptr, rwflag)
	FILEPTR *f;
	short *timeptr;
	int rwflag;
{
	if (rwflag) {
		struct tindex *ti = (struct tindex *)f->fc.index;
		ti->valid = 0;
	}
	return Fdatime(timeptr, (int)f->devinfo, rwflag);
}

static long ARGS_ON_STACK 
tos_close(f, pid)
	FILEPTR *f;
	int pid;
{
	LOCK *lck, **oldl;
	struct tindex *t;
	FILEPTR **old, *p;
	long r = 0;
	extern int flk;		/* set in main.c */

	t = (struct tindex *)(f->fc.index);
/* if this handle was locked, remove any locks held by the process
 */
	if (f->flags & O_LOCK) {
		TRACE(("tos_close: releasing locks (file mode: %x)", f->flags));
		oldl = &t->locks;
		lck = *oldl;
		while (lck) {
			if (lck->l.l_pid == pid) {
				*oldl = lck->next;
				if (flk)
					(void)Flock((int)f->devinfo, 1,
						lck->l.l_start, lck->l.l_len);
				wake(IO_Q, (long)lck);
				kfree(lck);
			} else {
				oldl = &lck->next;
			}
			lck = *oldl;
		}
	}

	if (f->links <= 0) {
/* remove f from the list of open file pointers on this index */
		t->valid = 0;
		old = &t->open;
		p = t->open;
		while (p && p != f) {
			old = &p->next;
			p = p->next;
		}
		assert(p);
		*old = f->next;
		f->next = 0;
		r = Fclose((int)f->devinfo);

/* if the file was marked for deletion, delete it */
		if (!t->open) {
			if (t->attr & FA_DELETE) {
				(void)Fdelete(t->name);
				t->name = 0;
			}
		}
	}
	return r;
}

/*
 * check for disk change: called by the kernel if Mediach returns a
 * non-zero value
 */

long ARGS_ON_STACK 
tos_dskchng(drv)
	int drv;
{
	char dlet;
	int i;
	struct tindex *ti;
	FILEPTR *f, *nextf;

	dlet = 'A' + drv;
MEDIA_DB(("tos_dskchng(%c)", dlet));
	ti = gl_ti;
	for (i = 0; i < NUM_INDICES; i++, ti++) {
		if (ti->name && ti->name[0] == dlet) {
#ifdef NEWWAY
	/* only free the name if this index not used by any cookie */
			if (ti->links != 0)
				ti->valid = 0;
			else
#endif /* NEWWAY */
			{
				kfree(ti->name);
				ti->name = 0;
			}
			if (!(ti->attr & FA_DIR)) {
				nextf = ti->open;
				while ( (f = nextf) != 0 ) {
					nextf = f->next;
					f->next = 0;
				}
			}
			ti->open = 0;
	/* if there are any cookies pointing at this name, they're not
	 * valid any more, so we will *want* to get an error if they're
	 * used.
	 */
		}
	}
	tmpindex.valid = 0;
/*
 * OK, make sure that GEMDOS knows to look for a change if we
 * ever use this drive again.
 */
	drvchanged[drv] = 1;
	return 1;
}

#ifdef NEWWAY
/* release/copy file cookies; these functions exist to keep
 * track of whether or not the kernel is still using a file
 */
long ARGS_ON_STACK
tos_release(fcookie *fc)
{
	struct tindex *ti = (struct tindex *)fc->index;

	if (ti->links <= 0) {
		FATAL("tos_release: link count of `%s' is %d", ti->name, ti->links);
	}
	ti->links--;
	COOKIE_DB(("tos_release: %s now has %d links", ti->name, ti->links));
	return 0;
}

long ARGS_ON_STACK
tos_dupcookie(fcookie *dest, fcookie *src)
{
	struct tindex *ti = (struct tindex *)src->index;

	if (ti->links <= 0) {
		FATAL("tos_dupcookie: link count of %s is %d", ti->name, ti->links);
	}
	ti->links++;
	COOKIE_DB(("tos_dupcookie: %s now has %d links", ti->name, ti->links));
	*dest = *src;
	return 0;
}
#endif

/*
 * utility function: sets the TOS DTA, and also records what directory
 * this was in. This is just to save us a call into the kernel if the
 * correct DTA has already been set.
 */

static void
do_setdta(dta)
	DTABUF *dta;
{
	if (dta != lastdta) {
		Fsetdta(dta);
		lastdta = dta;
	}
}

/*
 * routines for forcing a media change on drive "drv"
 */

static int chdrv;

/* new Getbpb function: when this is called, all the other
 * vectors can be un-installed
 */

static long ARGS_ON_STACK (*Oldgetbpb) P_((int));
static long ARGS_ON_STACK (*Oldmediach) P_((int));
static long ARGS_ON_STACK (*Oldrwabs) P_((int, void *, int, int, int, long));

static long  ARGS_ON_STACK
Newgetbpb(d)
	int d;
{
	if (d == chdrv) {
MEDIA_DB(("Newgetbpb(%c)", d+'A'));
if (Oldgetbpb == Newgetbpb) {
MEDIA_DB(("AARGH!!! BAD BPBs"));
}
		*((Func *)GETBPB) = Oldgetbpb;
		*((Func *)RWABS) = Oldrwabs;
		*((Func *)MEDIACH) = Oldmediach;
	}
	return (*Oldgetbpb)(d);
}

static long ARGS_ON_STACK
Newmediach(d)
	int d;
{
	if (d == chdrv) {
MEDIA_DB(("Newmediach(%c)", d+'A'));
		return 2;
	}
	return (*Oldmediach)(d);
}

static long ARGS_ON_STACK
Newrwabs(mode, buf, num, recno, dev, l)
	int mode;
	void *buf;
	int num, recno, dev;
	long l;
{
	if (dev == chdrv) {
MEDIA_DB(("Newrwabs"));
		return E_CHNG;
	}
	return (*Oldrwabs)(mode, buf, num, recno, dev, l);
}

static void
force_mediach(d)
	int d;
{
#ifdef FSFIRST_MEDIACH
	static char fname[] = "X:\\*.*";
#else
	long r;
	static char fname[] = "X:\\X";
#endif
	TRACE(("tosfs: disk change drive %c", d+'A'));
MEDIA_DB(("forcing media change on %c", d+'A'));

	chdrv = d;
	Oldrwabs = *((Func *)RWABS);
	Oldgetbpb = *((Func *)GETBPB);
	Oldmediach = *((Func *)MEDIACH);

	if (Oldrwabs == Newrwabs || Oldgetbpb == Newgetbpb ||
	    Oldmediach == Newmediach) {
		FORCE("tosfs: error in media change code");
	} else {
		*((Func *)RWABS) = Newrwabs;
		*((Func *)GETBPB) = Newgetbpb;
		*((Func *)MEDIACH) = Newmediach;
	}

	fname[0] = d + 'A';
MEDIA_DB(("calling GEMDOS"));
#ifdef FSFIRST_MEDIACH
	(void)Fsfirst(fname, 8);
#else	
	r = Fopen(fname, 0);
	if (r >= 0) Fclose((int)r);
#endif
MEDIA_DB(("done calling GEMDOS"));
	drvchanged[d] = 0;
	if ( *((Func *)RWABS) == Newrwabs ) {
		DEBUG(("WARNING: media change not performed correctly"));
		*((Func *)GETBPB) = Oldgetbpb;
		*((Func *)RWABS) = Oldrwabs;
		*((Func *)MEDIACH) = Oldmediach;
	}
}