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

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

/*
 * various file system interface things
 */

#include "mint.h"

#define PATH2COOKIE_DB(x) TRACE(x)

/* Following three for the fcookie cache - EKL */
#ifdef FCOOKIE_CACHE
static FC_CACHE fc_cache[FC_MAX_CACHE];
static FC_INDEX fc_index;
static top_type fc_top = (top_type) -2;
#endif

FILESYS *active_fs;
FILESYS *drives[NUM_DRIVES];
extern FILESYS tos_filesys;	/* declaration needed for debugging only */

/* "aliased" drives are different names
 * for real drives/directories
 * if drive d is an alias for c:\usr,
 * then alias_drv[3] == 2 (the real
 * drive) and aliases has bit (1L << 3)
 * set.
 * NOTE: if aliasdrv[d] is 0, then d is not an aliased drive,
 * otherwise d is aliased to drive aliasdrv[d]-1
 * (e.g. if drive A: is aliased to B:\FOO, then
 * aliasdrv[0] == 'B'-'A'+1 == 2). Always remember to
 * compensate for the extra 1 when dereferencing aliasdrv!
 */
int	aliasdrv[NUM_DRIVES];

FILEPTR *flist;		/* a list of free file pointers */

/* vector of valid drives, according to GEMDOS */
/* note that this isn't necessarily the same as what the BIOS thinks of
 * as valid
 */
long dosdrvs;

/*
 * Initialize a specific drive. This is called whenever a new drive
 * is accessed, or when media change occurs on an old drive.
 * Assumption: at this point, active_fs is a valid pointer
 * to a list of file systems.
 */

/* table of processes holding locks on drives */
extern PROC *dlockproc[];	/* in dosdir.c */

void
init_drive(i)
	int i;
{
	long r;
	FILESYS *fs;
	fcookie root_dir;

	TRACE(("init_drive(%c)", i+'A'));

	drives[i] = 0;		/* no file system */
	if (i >= 0 && i < NUM_DRIVES) {
		if (dlockproc[i]) return;
	}

	for (fs = active_fs; fs; fs = fs->next) {
		r = (*fs->root)(i, &root_dir);
		if (r == 0) {
			drives[i] = root_dir.fs;
			release_cookie(&root_dir);
			break;
		}
	}
}

/*
 * initialize the file system
 */

#define NUMFPS	40	/* initial number of file pointers */

void
init_filesys()
{
	static FILEPTR initial[NUMFPS+1];
	int i;
	extern FILESYS tos_filesys, bios_filesys, pipe_filesys,
		proc_filesys, uni_filesys;

/* get the vector of connected GEMDOS drives */
	dosdrvs = Dsetdrv(Dgetdrv()) | drvmap();

#ifdef FCOOKIE_CACHE
        cache_init();
#endif

/* set up some initial file pointers */
	for (i = 0; i < NUMFPS; i++) {
		initial[i].devinfo = (ulong) (&initial[i+1]);
	}
	initial[NUMFPS].devinfo = 0;
	flist = initial;

/* set up the file systems */
	tos_filesys.next = 0;
	bios_filesys.next = &tos_filesys;
	pipe_filesys.next = &bios_filesys;
	proc_filesys.next = &pipe_filesys;
	uni_filesys.next = &proc_filesys;

	active_fs = &uni_filesys;

/* initialize the BIOS file system */
	biosfs_init();

/* initialize the unified file system */
	unifs_init();
}


/* uk: go through the list of file systems and call their sync() function
 *     if they wish to.
 */
long
s_ync()
{
	FILESYS *fs;

	TRACE(("Syncing file systems..."));
	for (fs = active_fs;  fs;  fs = fs->next)
	{
		if (fs->fsflags & FS_DO_SYNC)
			(*fs->sync)();
	}
	TRACE(("Syncing done."));
	return 0;
}


/*
 * load file systems from disk
 * this routine is called after process 0 is set up, but before any user
 * processes are run
 *
 * NOTE that a number of directory changes take place here: we look first
 * in the current directory, then in the directory \mint.
 */

typedef FILESYS * ARGS_ON_STACK (*FSFUNC) P_((struct kerinfo *));

/* uk: made this lie outside of functions, as load_filesys() and
 *     load_devdriver() need access to it.
 */
#define NPATHS 3
static const char *const ext_paths[NPATHS] = {"", "\\MINT", "\\MULTITOS"};

void
load_filesys()
{
	long r;
	BASEPAGE *b;
	FILESYS *fs;
	FSFUNC initf;
	static DTABUF dta;
	int i;
	extern struct kerinfo kernelinfo; /* in main.c */
	char curpath[PATH_MAX];
	MEMREGION *xfsreg;

	curproc->dta = &dta;
	d_getpath(curpath,0);

	for (i = 0; i < NPATHS; i++) {
	    if (*ext_paths[i]) {
/* don't bother checking the current directory twice! */
		    if (!stricmp(ext_paths[i],curpath))
			r = -1;
		    else
			r = d_setpath(ext_paths[i]);
	    }
	    else
		    r = 0;

	    if (r == 0)
		    r = f_sfirst("*.xfs", 0);

	    while (r == 0) {
		b = (BASEPAGE *)p_exec(3, dta.dta_name, (char *)"", (char *)0);
		if ( ((long)b) < 0 ) {
			DEBUG(("Error loading file system %s", dta.dta_name));
			r = f_snext();
			continue;
		}
	/* we leave a little bit of slop at the end of the loaded stuff */
		m_shrink(0, (virtaddr)b, 512 + b->p_tlen + b->p_dlen + b->p_blen);
		initf = (FSFUNC)b->p_tbase;
		TRACE(("initializing %s", dta.dta_name));
		fs = (*initf)(&kernelinfo);

		if (fs) {
			TRACE(("%s loaded OK", dta.dta_name));
	/* put the loaded XFS into super accesible memory */
			xfsreg = addr2region( (long) b );
			mark_region(xfsreg, PROT_S);

	/* link it into the list of drivers */
	/* uk: but only if it has not installed itself via Dcntl()
	 *     after checking if file system is already installed,
	 *     so we know for sure that each file system in at most
	 *     once in the chain (important for removal!)
	 * also note: this doesn't preclude loading two different
	 * instances of the same file system driver, e.g. it's perfectly
	 * OK to have a "cdromy1.xfs" and "cdromz2.xfs"; the check below
	 * just makes sure that a given instance of a file system is
	 * installed at most once. I.e., it prevents cdromy1.xfs from being
	 * installed twice.
	 */
			if ((FILESYS*)1L != fs) {
				FILESYS *f = active_fs;
				for (;  f;  f = f->next)
					if (f == fs)
						break;
				if (!f) {   /* we ran completly through the list */
					fs->next = active_fs;
					active_fs = fs;
				}
			}
		} else {
			DEBUG(("%s returned null", dta.dta_name));
			m_free((virtaddr)b);
		}
		r = f_snext();
	    }
	}

#if 0
/* here, we invalidate all old drives EXCEPT for ones we're already using (at
 * this point, only the bios devices should be open)
 * this gives newly loaded file systems a chance to replace the
 * default tosfs.c
 */
	for (i = 0; i < NUM_DRIVES; i++) {
		if (d_lock(1, i) == 0)	/* lock if possible */
			d_lock(0, i);	/* and then unlock */
	}
#endif
}


/*
 * uk: load device driver in files called *.xdd (external device driver)
 *     from disk
 * maybe this should go into biosfs.c ??
 *
 * this routine is called after process 0 is set up, but before any user
 * processes are run, but before the loadable file systems come in,
 * so they can make use of external device drivers
 *
 * NOTE that a number of directory changes take place here: we look first
 * in the current directory, then in the directory \mint, and finally
 * the d_lock() calls force us into the root directory.
 * ??? what d_lock() calls ???
 */

typedef DEVDRV * ARGS_ON_STACK (*DEVFUNC) P_((struct kerinfo *));

#define DEV_SELFINST  ((DEVDRV*)1L)  /* dev driver did dcntl() already */

void
load_devdriver()
{
	long r;
	BASEPAGE *b;
	DEVDRV *dev;
	DEVFUNC initf;
	struct dev_descr the_dev;
	static DTABUF dta;
	int i;
	extern struct kerinfo kernelinfo; /* in main.c */
	char curpath[PATH_MAX];
	char dev_name[PATH_MAX];  /* a bit long, but one never knows... */
	char ch, *p;
	MEMREGION *xddreg;


	curproc->dta = &dta;
	d_getpath(curpath,0);

	for (i = 0; i < NPATHS; i++) {
	    if (*ext_paths[i]) {
/* don't bother checking the current directory twice! */
		    if (!stricmp(ext_paths[i],curpath))
			r = -1;
		    else
			r = d_setpath(ext_paths[i]);
	    }
	    else
		    r = 0;

	    if (r == 0)
		    r = f_sfirst("*.xdd", 0);

	    while (r == 0) {
		b = (BASEPAGE *)p_exec(3, dta.dta_name, (char *)"", (char *)0);
		if ( ((long)b) < 0 ) {
			DEBUG(("Error loading device driver %s", dta.dta_name));
			r = f_snext();
			continue;
		}
	/* we leave a little bit of slop at the end of the loaded stuff */
		m_shrink(0, (virtaddr)b, 512 + b->p_tlen + b->p_dlen + b->p_blen);
		initf = (DEVFUNC)b->p_tbase;
		TRACE(("initializing %s", dta.dta_name));
		dev = (*initf)(&kernelinfo);

		if (dev) {
			if (DEV_SELFINST != dev) {
	/* we need to install the device driver ourselves */
				zero((char *)&the_dev, SIZEOF(the_dev));
				the_dev.driver = dev;
				p = dta.dta_name;
	/* copy the dev. driver name, converting to lower case */
				while (*p && *p != '.') {
					*p = tolower(*p);
					p++;
				}
				ch = *p;
				*p = '\0';  /* we dont want the extension */
				strcpy(dev_name, "u:\\dev\\");
				strcat(dev_name, dta.dta_name);
				*p = ch;
				r = d_cntl(DEV_INSTALL, dev_name, (long)&the_dev);
				if (r <= 0) {
					DEBUG(("Error installing device driver %s", dta.dta_name));
					r = f_snext();
					continue;
				}
			}
			TRACE(("%s loaded OK", dta.dta_name));
	/* put the loaded XDD into super accesible memory */
			xddreg = addr2region( (long) b );
			mark_region(xddreg, PROT_S);
		} else {
			DEBUG(("%s returned null", dta.dta_name));
			m_free((virtaddr)b);
		}
		r = f_snext();
	    }
	}
}
 

void
close_filesys()
{
	PROC *p;
	FILEPTR *f;
	int i;

	TRACE(("close_filesys"));
/* close every open file */
	for (p = proclist; p; p = p->gl_next) {
		for (i = MIN_HANDLE; i < MAX_OPEN; i++) {
			if ( (f = p->handle[i]) != 0) {
				if (p->wait_q == TSR_Q || p->wait_q == ZOMBIE_Q)
					ALERT("Open file for dead process?");
				do_pclose(p, f);
			}
		}
	}
}

#ifdef FCOOKIE_CACHE
void cache_init (void)
{
        top_type i;

        if (fc_top >= 0) {
                for (i = 0; i <= fc_top; i++) {
                        kfree (fc_cache[fc_index.entry[i]].name);
                        clobber_cookie (&fc_cache[fc_index.entry[i]].file);
                }
        }

        for (i = 0; i < FC_MAX_CACHE; i++)
                fc_index.entry[i] = i;
        fc_top = (top_type) -1;
}
#endif

/*
 * "media change" routine: called when a media change is detected on device
 * d, which may or may not be a BIOS device. All handles associated with
 * the device are closed, and all directories invalidated. This routine
 * does all the dirty work, and is called automatically when
 * disk_changed detects a media change.
 */

void ARGS_ON_STACK 
changedrv(d)
	unsigned d;
{
	PROC *p;
	int i;
	FILEPTR *f;
	FILESYS *fs;
	SHTEXT *stext, **old;
	extern SHTEXT *text_reg;	/* in mem.c */
	DIR *dirh;
	fcookie dir;
	int warned = (d & 0xf000) == PROC_RDEV_BASE;
	long r;

/* if an aliased drive, change the *real* device */
	if (d < NUM_DRIVES && aliasdrv[d]) {
		d = aliasdrv[d] - 1;	/* see NOTE above */
	}

/* re-initialize the device, if it was a BIOS device */
	if (d < NUM_DRIVES) {
		fs = drives[d];
		if (fs) {
			(void)(*fs->dskchng)(d);
		}
		init_drive(d);
	}

	for (p = proclist; p; p = p->gl_next) {
	/* invalidate all open files on this device */
		for (i = MIN_HANDLE; i < MAX_OPEN; i++) {
			if (((f = p->handle[i]) != 0) &&
				(f != (FILEPTR *)1) && (f->fc.dev == d)) {
			    if (!warned) {
				ALERT(
"Files were open on a changed drive (0x%x)!", d);
				warned++;
			    }

/* we set f->dev to NULL to indicate to do_pclose that this is an
 * emergency close, and that it shouldn't try to make any
 * calls to the device driver since the file has gone away
 */
			    f->dev = NULL;
			    (void)do_pclose(p, f);
/* we could just zero the handle, but this could lead to confusion if
 * a process doesn't realize that there's been a media change, Fopens
 * a new file, and gets the same handle back. So, we force the
 * handle to point to /dev/null.
 */
			    p->handle[i] =
				do_open("U:\\DEV\\NULL", O_RDWR, 0, (XATTR *)0);
			}
		}

	/* terminate any active directory searches on the drive */
		for (i = 0; i < NUM_SEARCH; i++) {
			dirh = &p->srchdir[i];
			if (p->srchdta[i] && dirh->fc.fs && dirh->fc.dev == d) {
				TRACE(("closing search for process %d", p->pid));
				release_cookie(&dirh->fc);
				dirh->fc.fs = 0;
				p->srchdta[i] = 0;
			}
		}

		for (dirh = p->searches; dirh; dirh = dirh->next) {
		    /* If this search is on the changed drive, release
		       the cookie, but do *not* free it, since the
		       user could later call closedir on it. */
			if (dirh->fc.fs && dirh->fc.dev == d) {
				release_cookie (&dirh->fc);
				dirh->fc.fs = 0;
			}
		}
		    
		if (d >= NUM_DRIVES) continue;

	/* change any active directories on the device to the (new) root */
		fs = drives[d];
		if (fs) {
			r = (*fs->root)(d, &dir);
			if (r != E_OK) dir.fs = 0;
		} else {
			dir.fs = 0; dir.dev = d;
		}

		for (i = 0; i < NUM_DRIVES; i++) {
			if (p->root[i].dev == d) {
				release_cookie(&p->root[i]);
				dup_cookie(&p->root[i], &dir);
			}
			if (p->curdir[i].dev == d) {
				release_cookie(&p->curdir[i]);
				dup_cookie(&p->curdir[i], &dir);
			}
		}
		release_cookie(&dir);
	}

/* free any file descriptors associated with shared text regions */
	for (old = &text_reg; 0 != (stext = *old);) {
		f = stext->f;
		if (f->fc.dev == d) {
			f->dev = NULL;
			do_pclose(rootproc, f);
			stext->f = 0;
/* free region if unattached */
			if (stext->text->links == 0xffff) {
				stext->text->links = 0;
				stext->text->mflags &= ~(M_SHTEXT|M_SHTEXT_T);
				free_region(stext->text);
				*old = stext->next;
				kfree(stext);
				continue;
			}
/* else clear `sticky bit' */
			stext->text->mflags &= ~M_SHTEXT_T;
		}
		old = &stext->next;
	}
#ifdef FCOOKIE_CACHE
        for (i=0; i <= fc_top; i++)
                if (fc_cache[fc_index.entry[i]].file.dev == d)
                        kill_cache(&fc_cache[fc_index.entry[i]].dir,
				fc_cache[fc_index.entry[i]].name);
#endif
}

/*
 * check for media change: if the drive has changed, call changedrv to
 * invalidate any open files and file handles associated with it, and
 * call the file system's media change routine.
 * returns: 0 if no change, 1 if change, negative number for error
 */

int
disk_changed(d)
	int d;
{
	short r;
	FILESYS *fs;
	static char tmpbuf[8192];

/* for now, only check BIOS devices */
	if (d < 0 || d >= NUM_DRIVES)
		return 0;
/* watch out for aliased drives */
	if (aliasdrv[d]) {
		d = aliasdrv[d] - 1;
		if (d < 0 || d >= NUM_DRIVES)
			return 0;
	}

/* has the drive been initialized yet? If not, then initialize it and return
 * "no change"
 */
	fs = drives[d];
	if (!fs) {
		TRACE(("drive %c not yet initialized", d+'A'));
		changedrv(d);
		return 0;
	}

/* We have to do this stuff no matter what, because someone may have installed
 * vectors to force a media change...
 * PROBLEM: AHDI may get upset if the drive isn't valid.
 * SOLUTION: don't change the default PSEUDODRIVES setting!
 */

TRACE(("calling mediach(%d)",d));
	r = (int)mediach(d);
TRACE(("mediach(%d) == %d", d, r));

	if (r < 0) {
/* KLUDGE: some .XFS drivers don't install BIOS vectors, and so we'll
 * always get EUNDEV back from them. This isn't recommended (since there
 * are other programs than MiNT that may ask for BIOS functions from
 * any installed drives). This is a temporary work-around until those
 * .XFSes are changed to either install BIOS vectors or to use the
 * new U: Dcntl() calls to install themselves.
 * Note that EUNDEV must be tested for drives A-C, or else booting may
 * not work properly.
 */
		if (d > 2 && r == EUNDEV)
			return 0;	/* assume no change */
		else
			return r;
	}
	if (r == 1) {		/* drive _may_ have changed */
		r = rwabs(0, tmpbuf, 1, 0, d, 0L);	/* check the BIOS */
		if (r != E_CHNG) {			/* nope, no change */
			TRACE(("rwabs returned %d", r));
			return (r < 0) ? r : 0;
		}
		r = 2;			/* drive was definitely changed */
	}
	if (r == 2) {
		TRACE(("definite media change"));
		fs = drives[d];		/* get filesystem associated with drive */
		if ((*fs->dskchng)(d)) { /* does the fs agree that it changed? */
			drives[d] = 0;
			changedrv(d);	/* yes -- do the change */
			return 1;
		}
	}
	return 0;
}

/*
 * routines for parsing path names
 */

#ifdef FCOOKIE_CACHE
/* cache_lookup  - This is a cached function that replaces the fs->lookup
 * code in the path2cookie routines.  Basically, it pretends to do a lookup
 * just like the filesystem would.  It searches the cache from the top
 * down and when it finds a match it moves that cookie to the top of the
 * stack (instead of moving cache blocks it moves a 16-bit index).  When
 * the cache lookup fails, it increases top and calls the filesystem lookup.
 * When it runs out of cache entries, it rotates the cache down by 2 entries
 * returning the bottom 2 to the filesystem.
 */

long
cache_lookup (fcookie * dir, char * name, fcookie * res)
{
        long r = 0;
        top_type i,j;

        i = fc_top;
        while (i >= 0) {
                j = fc_index.entry[i];
                if (samefile(dir,&fc_cache[j].dir)) {
                        if (fc_cache[j].dir.fs->fsflags & FS_CASESENSITIVE) {
                                if (!strcmp(name,fc_cache[j].name))
                                        goto foundit;
                        } else if (!stricmp(name,fc_cache[j].name)) {
foundit:
                                for (; i < fc_top; i++)
                                        fc_index.entry[i] = fc_index.entry[i+1];
                                fc_index.entry[fc_top] = j;
                                dup_cookie(res,&fc_cache[j].file);
                                return r;
                        }
                }
                i--;
        }
        if (!(r = (*dir->fs->lookup)(dir, name, res))) {
                if (fc_top > -2) {
                        fc_top++;
                        if (fc_top >= FC_MAX_CACHE) {
                                long k;

                                k = fc_index.macroval[0];
                                for (i = 0; i < 2; i++) {
                                        kfree(fc_cache[fc_index.entry[i]].name);
		                        clobber_cookie(&fc_cache[fc_index.entry[i]].file);
                                }
                                for (i = 0; i < ((FC_MAX_CACHE/2)-1); i++)
                		        fc_index.macroval[i] = fc_index.macroval[i+1];
                                fc_index.macroval[(FC_MAX_CACHE/2)-1] = k;
                                fc_top -= 2;
                        }
                        dup_cookie(&fc_cache[fc_index.entry[fc_top]].dir,dir);
        		fc_cache[fc_index.entry[fc_top]].name = kmalloc (strlen(name)+1);
                        strcpy (fc_cache[fc_index.entry[fc_top]].name, name);
                        dup_cookie(&fc_cache[fc_index.entry[fc_top]].file,res);
                }
        }
        return r;
}
#endif

#ifndef DIRSEP_SLASH
#define DIRSEP(p) ((p) == '\\')
#else
/* EKL: hacked DIRSEP - why not use either slash? */
#define DIRSEP(p) ( ((p) == '\\') || ((p) == '/') )
#endif

/*
 * relpath2cookie converts a TOS file name into a file cookie representing
 * the directory the file resides in, and a character string representing
 * the name of the file in that directory. The character string is
 * copied into the "lastname" array. If lastname is NULL, then the cookie
 * returned actually represents the file, instead of just the directory
 * the file is in.
 *
 * note that lastname, if non-null, should be big enough to contain all the
 * characters in "path", since if the file system doesn't want the kernel
 * to do path name parsing we may end up just copying path to lastname
 * and returning the current or root directory, as appropriate
 *
 * "relto" is the directory relative to which the search should start.
 * if you just want the current directory, use path2cookie instead.
 *
 */

#define MAX_LINKS 4

long
relpath2cookie(relto, path, lastname, res, depth)
	fcookie *relto;
	const char *path;
	char *lastname;
	fcookie *res;
	int depth;
{
	fcookie dir;
	int drv;
	int len;
	char c, *s;
	XATTR xattr;
	static char newpath[16] = "U:\\DEV\\";
	char temp2[PATH_MAX];
	char linkstuff[PATH_MAX];
	long r;

/* dolast: 0 == return a cookie for the directory the file is in
 *         1 == return a cookie for the file itself, don't follow links
 *	   2 == return a cookie for whatever the file points at
 */
	int dolast = 0;
	int i = 0;

	if (!lastname) {
		dolast = 1;
		lastname = temp2;
	} else if (lastname == follow_links) {
		dolast = 2;
		lastname = temp2;
	}

	*lastname = 0;

PATH2COOKIE_DB(("relpath2cookie(%s, dolast=%d, depth=%d)", path, dolast, depth));

	if (depth > MAX_LINKS) {
		DEBUG(("Too many symbolic links"));
		return ELOOP;
	}
/* special cases: CON:, AUX:, etc. should be converted to U:\DEV\CON,
 * U:\DEV\AUX, etc.
 */
	if (strlen(path) == 4 && path[3] == ':') {
		strncpy(newpath+7, path, 3);
		path = newpath;
	}

/* first, check for a drive letter */
/* BUG: a '\' at the start of a symbolic link is relative to the current
 * drive of the process, not the drive the link is located on
 */
	if (path[1] == ':') {
		c = path[0];
		if (c >= 'a' && c <= 'z')
			drv = c - 'a';
		else if (c >= 'A' && c <= 'Z')
			drv = c - 'A';
		else
			goto nodrive;
		path += 2;
		i = 1;		/* remember that we saw a drive letter */
	} else {
nodrive:
		drv = curproc->curdrv;
	}

/* see if the path is rooted from '\\' */
	if (DIRSEP(*path)) {
		while(DIRSEP(*path))path++;
		dup_cookie(&dir, &curproc->root[drv]);
	} else {
		if (i)	{	/* an explicit drive letter was given */
			dup_cookie(&dir, &curproc->curdir[drv]);
		}
		else
			dup_cookie(&dir, relto);
	}

	if (!dir.fs) {
		changedrv(dir.dev);
		dup_cookie(&dir, &curproc->root[drv]);
	}

	if (!dir.fs) {
		DEBUG(("path2cookie: no file system: returning EDRIVE"));
		return EDRIVE;
	}

	/* here's where we come when we've gone across a mount point */
	
restart_mount:

	if (!*path) {		/* nothing more to do */
PATH2COOKIE_DB(("relpath2cookie: no more path, returning 0"));
		*res = dir;
		return 0;
	}

/* see if there has been a disk change; if so, return E_CHNG.
 * path2cookie will restart the search automatically; other functions
 * that call relpath2cookie directly will have to fail gracefully
 */
	if ((r = disk_changed(dir.dev)) != 0) {
		release_cookie(&dir);
		if (r > 0) r = E_CHNG;
PATH2COOKIE_DB(("relpath2cookie: returning %d", r));
		return r;
	}


	if (dir.fs->fsflags & FS_KNOPARSE) {
		if (!dolast) {
PATH2COOKIE_DB(("fs is a KNOPARSE, nothing to do"));
			strncpy(lastname, path, PATH_MAX-1);
			lastname[PATH_MAX - 1] = 0;
			r = 0;
			*res = dir;
		} else {
PATH2COOKIE_DB(("fs is a KNOPARSE, calling lookup"));
			r = (*dir.fs->lookup)(&dir, path, res);
			if (r == EMOUNT) {	/* hmmm... a ".." at a mount point, maybe */
				fcookie mounteddir;
				r = (*dir.fs->root)(dir.dev, &mounteddir);
				if (r == 0 && drv == UNIDRV) {
					if (dir.fs == mounteddir.fs &&
					    dir.index == mounteddir.index &&
					    dir.dev == mounteddir.dev) {
						release_cookie(&dir);
						release_cookie(&mounteddir);
						dup_cookie(&dir, &curproc->root[UNIDRV]);
						TRACE(("path2cookie: restarting from mount point"));
						goto restart_mount;
					}
				} else {
					if (r == 0)
						release_cookie(&mounteddir);
					r = 0;
				}
			}
			release_cookie(&dir);
		}
		PATH2COOKIE_DB(("relpath2cookie: returning %ld", r));
		return r;
	}


/* parse all but (possibly) the last component of the path name */
/* rules here: at the top of the loop, &dir is the cookie of
 * the directory we're in now, xattr is its attributes, and res is unset
 * at the end of the loop, &dir is unset, and either r is nonzero
 * (to indicate an error) or res is set to the final result
 */
	r = (dir.fs->getxattr)(&dir, &xattr);
	if (r) {
		DEBUG(("couldn't get directory attributes"));
		release_cookie(&dir);
		return EINTRN;
	}

	while (*path) {

	/* now we must have a directory, since there are more things in the path */
		if ((xattr.mode & S_IFMT) != S_IFDIR) {
PATH2COOKIE_DB(("relpath2cookie: not a directory, returning EPTHNF"));
			release_cookie(&dir);
			r = EPTHNF;
			break;
		}
	/* we must also have search permission for the directory */
		if (denyaccess(&xattr, S_IXOTH)) {
			DEBUG(("search permission in directory denied"));
			release_cookie(&dir);
			r = EPTHNF;
			break;
		}

	/* if there's nothing left in the path, we can break here */
		if (!*path) {
PATH2COOKIE_DB(("relpath2cookie: no more path, breaking (1)"));
			*res = dir;
			break;
		}
	/* next, peel off the next name in the path */
		len = 0;
		s = lastname;
		c = *path;
		while (c && !DIRSEP(c)) {
			if (len++ < PATH_MAX)
				*s++ = c;
			c = *++path;
		}
		*s = 0;

	/* if there are no more names in the path, and we don't want
	 * to actually look up the last name, then we're done
	 */
		if (dolast == 0 && !*path) {
			*res = dir;
PATH2COOKIE_DB(("relpath2cookie: no more path, breaking (2)"));
			break;
		}


	/* 
	 * skip trailing slashes
	 */
		while (DIRSEP(*path)) path++;

PATH2COOKIE_DB(("relpath2cookie: looking up [%s]", lastname));

#ifdef FCOOKIE_CACHE
                if (!(dir.fs->fsflags & FS_NO_C_CACHE))
                        r = cache_lookup(&dir, lastname, res);
                else
#endif
		r = (*dir.fs->lookup)(&dir, lastname, res);
		if (r == EMOUNT) {
			fcookie mounteddir;
			r = (*dir.fs->root)(dir.dev, &mounteddir);
			if (r == 0 && drv == UNIDRV) {
				if (samefile(&dir, &mounteddir)) {
					release_cookie(&dir);
					release_cookie(&mounteddir);
					dup_cookie(&dir, &curproc->root[UNIDRV]);
					TRACE(("path2cookie: restarting from mount point"));
					goto restart_mount;
				} else if (r == 0) {
					r = EINTRN;
					release_cookie(&mounteddir);
					release_cookie(&dir);
					break;
				}
			} else if (r == 0) {
				release_cookie(&mounteddir);
			} else {
				release_cookie(&dir);
				break;
			}
		} else if (r) {
			if (r == EFILNF && *path) {
			/* the "file" we didn't find was treated as a directory */
				r = EPTHNF;
			}
			release_cookie(&dir);
			break;
		}

	/* check for a symbolic link */
		r = (res->fs->getxattr)(res, &xattr);
		if (r != 0) {
			DEBUG(("path2cookie: couldn't get file attributes"));
			release_cookie(&dir);
			release_cookie(res);
			break;
		}

	/* if the file is a link, and we're following links, follow it */
		if ( (xattr.mode & S_IFMT) == S_IFLNK && (*path || dolast > 1)) {
			r = (res->fs->readlink)(res, linkstuff, PATH_MAX);
			release_cookie(res);
			if (r) {
				DEBUG(("error reading symbolic link"));
				release_cookie(&dir);
				break;
			}
			r = relpath2cookie(&dir, linkstuff, follow_links, res,
						depth+1);
			release_cookie(&dir);
			if (r) {
				DEBUG(("error following symbolic link"));
				break;
			}
			dir = *res;
			(void)(res->fs->getxattr)(res, &xattr);
		} else {
			release_cookie(&dir);
			dir = *res;
		}
	}

	PATH2COOKIE_DB(("relpath2cookie: returning %ld", r));
	return r;
}

#define MAX_TRYS 8

long
path2cookie(path, lastname, res)
	const char *path;
	char *lastname;
	fcookie *res;
{
	fcookie *dir;
	long r;
/* AHDI sometimes will keep insisting that a media change occured;
 * we limit the number of retrys to avoid hanging the system
 */
	int trycnt = 0;

	dir = &curproc->curdir[curproc->curdrv];

	do {
		r = relpath2cookie(dir, path, lastname, res, 0);
		if (r == E_CHNG)
			DEBUG(("path2cookie: restarting due to media change"));
	} while (r == E_CHNG && trycnt++ < MAX_TRYS);

	return r;
}

#ifdef FCOOKIE_CACHE
void
kill_cache (fcookie * bad, char * name)
{
        top_type save,i,j;

        for (i=0; i <= fc_top; i++)
                if ((samefile(bad,&fc_cache[fc_index.entry[i]].dir)) &&
                (!stricmp(name,fc_cache[fc_index.entry[i]].name)) ) {
                        save = fc_index.entry[i];
                        kfree(fc_cache[save].name);
                        clobber_cookie(&fc_cache[save].file);
                        for (j = i; j < fc_top; j++)
                                fc_index.entry[j] = fc_index.entry[j+1];
                        fc_index.entry[fc_top] = save;
                        fc_top--;
                }
}
#endif

/*
 * release_cookie: tell the file system owner that a cookie is no
 * longer in use by the kernel
 *
 * release_cookie doesn't release anymore unless there is no entry in
 * the cookie cache.  Otherwise, we just let the cookie get released
 * through clobber_cookie when the cache fills or is killed through the
 * routine above - EKL
 */
void
release_cookie(fc)
	fcookie *fc;
{
#ifdef FCOOKIE_CACHE
        top_type i;

        /* If cookie is not in cache, clobber it!
         * Otherwise, just do nothing I guess! - EKL
         */

        i = fc_top;
        while (i >= (top_type) 0) {
                if ( (samefile(fc,&fc_cache[fc_index.entry[i]].file)) )
                        return;
                else i--;
        }
	return clobber_cookie(fc);
}

/* This routine is called when we really are done with a cookie.
 * It is called when a released cookie is not in the cache, or by
 * kill_cache or cache_lookup need to get rid of the cookie for a
 * cache entry  - EKL
 */

void
clobber_cookie(fc)
        fcookie *fc;
{
#endif
	FILESYS *fs;

	if (fc) {
		fs = fc->fs;
		if (fs && fs->release) {
			(void)(*fs->release)(fc);
		}
	}
}

/*
 * Make a new cookie (newc) which is a duplicate of the old cookie
 * (oldc). This may be something the file system is interested in,
 * so we give it a chance to do the duplication; if it doesn't
 * want to, we just copy.
 */

void
dup_cookie(newc, oldc)
	fcookie *newc, *oldc;
{
	FILESYS *fs = oldc->fs;

	if (fs && fs->release && fs->dupcookie) {
		(void)(*fs->dupcookie)(newc, oldc);
	} else {
		*newc = *oldc;
	}
}

/*
 * new_fileptr, dispose_fileptr: allocate (deallocate) a file pointer
 */

FILEPTR *
new_fileptr()
{
	FILEPTR *f;

	if ((f = flist) != 0) {
		flist = f->next;
		f->next = 0;
		return f;
	}
	f = kmalloc(SIZEOF(FILEPTR));
	if (!f) {
		FATAL("new_fileptr: out of memory");
	}
	else {
		f->next = 0;
	}
	return f;
}

void
dispose_fileptr(f)
	FILEPTR *f;
{
	if (f->links != 0) {
		FATAL("dispose_fileptr: f->links == %d", f->links);
	}
	f->next = flist;
	flist = f;
}

/*
 * denyshare(list, f): "list" points at the first FILEPTR in a
 * chained list of open FILEPTRS referring to the same file;
 * f is a newly opened FILEPTR. Every FILEPTR in the given list is
 * checked to see if its "open" mode (in list->flags) is compatible with
 * the open mode in f->flags. If not (for example, if f was opened with
 * a "read" mode and some other file has the O_DENYREAD share mode),
 * then 1 is returned. If all the open FILEPTRs in the list are
 * compatible with f, then 0 is returned.
 * This is not as complicated as it sounds. In practice, just keep a
 * list of open FILEPTRs attached to each file, and put something like
 * 	if (denyshare(thisfile->openfileptrlist, newfileptr))
 *		return EACCDN;
 * in the device open routine.
 */

int ARGS_ON_STACK 
denyshare(list, f)
	FILEPTR *list, *f;
{
	int newrm, newsm;	/* new read and sharing mode */
	int oldrm, oldsm;	/* read and sharing mode of already opened file */
	extern MEMREGION *tofreed;
	MEMREGION *m = tofreed;
	int i;

	newrm = f->flags & O_RWMODE;
	newsm = f->flags & O_SHMODE;

/*
 * O_EXEC gets treated the same as O_RDONLY for our purposes
 */
	if (newrm == O_EXEC) newrm = O_RDONLY;

/* New meaning for O_COMPAT: deny write access to all _other_
 * processes.
 */

	for ( ; list; list = list->next) {
		oldrm = list->flags & O_RWMODE;
		if (oldrm == O_EXEC) oldrm = O_RDONLY;
		oldsm = list->flags & O_SHMODE;
		if (oldsm == O_DENYW || oldsm == O_DENYRW) {
		 	if (newrm != O_RDONLY) {
/* conflict because of unattached shared text region? */
				if (!m && NULL != (m = find_text_seg(list))) {
					if (m->links == 0xffff)
						continue;
					m = 0;
				}
				DEBUG(("write access denied"));
				return 1;
			}
		}
		if (oldsm == O_DENYR || oldsm == O_DENYRW) {
			if (newrm != O_WRONLY) {
				DEBUG(("read access denied"));
				return 1;
			}
		}
		if (newsm == O_DENYW || newsm == O_DENYRW) {
			if (oldrm != O_RDONLY) {
				DEBUG(("couldn't deny writes"));
				return 1;
			}
		}
		if (newsm == O_DENYR || newsm == O_DENYRW) {
			if (oldrm != O_WRONLY) {
				DEBUG(("couldn't deny reads"));
				return 1;
			}
		}
/* If either sm == O_COMPAT, then we check to make sure
   that the file pointers are owned by the same process (O_COMPAT means
   "deny writes to any other processes"). This isn't quite the same
   as the Atari spec, which says O_COMPAT means "deny access to other
   processes." We should fix the spec.
 */
		if ((newsm == O_COMPAT && newrm != O_RDONLY && oldrm != O_RDONLY) ||
		    (oldsm == O_COMPAT && newrm != O_RDONLY)) {
			for (i = MIN_HANDLE; i < MAX_OPEN; i++) {
				if (curproc->handle[i] == list)
					goto found;
			}
		/* old file pointer is not open by this process */
			DEBUG(("O_COMPAT file was opened for writing by another process"));
			return 1;
		found:
			;	/* everything is OK */
		}
	}
/* cannot close shared text regions file here... have open do it. */
	if (m)
		tofreed = m;
	return 0;
}

/*
 * denyaccess(XATTR *xattr, unsigned perm): checks to see if the access
 * specified by perm (which must be some combination of S_IROTH, S_IWOTH,
 * and S_IXOTH) should be granted to the current process
 * on a file with the given extended attributes. Returns 0 if access
 * by the current process is OK, 1 if not.
 */

int
ngroupmatch(group)
	int group;
{
	int i;

	for (i=0; i<curproc->ngroups; i++)
		if (curproc->ngroup[i] == group)
			return 1;

	return 0;
}

int
denyaccess(xattr, perm)
	XATTR *xattr;
	unsigned perm;
{
	unsigned mode;

/* the super-user can do anything! */
	if (curproc->euid == 0)
		return 0;

	mode = xattr->mode;
	if (curproc->euid == xattr->uid)
		perm = perm << 6;
	else if (curproc->egid == xattr->gid)
		perm = perm << 3;
	else if (ngroupmatch(xattr->gid))
		perm = perm << 3;

	if ((mode & perm) != perm) return 1;	/* access denied */
	return 0;
}

/*
 * Checks a lock against a list of locks to see if there is a conflict.
 * This is a utility to be used by file systems, somewhat like denyshare
 * above. Returns 0 if there is no conflict, or a pointer to the
 * conflicting LOCK structure if there is.
 *
 * Conflicts occur for overlapping locks if the process id's are
 * different and if at least one of the locks is a write lock.
 *
 * NOTE: we assume before being called that the locks have been converted
 * so that l_start is absolute. not relative to the current position or
 * end of file.
 */

LOCK * ARGS_ON_STACK 
denylock(list, lck)
	LOCK *list, *lck;
{
	LOCK *t;
	unsigned long tstart, tend;
	unsigned long lstart, lend;
	int pid = curproc->pid;
	int ltype;

	ltype = lck->l.l_type;
	lstart = lck->l.l_start;

	if (lck->l.l_len == 0)
		lend = 0xffffffffL;
	else
		lend = lstart + lck->l.l_len - 1;

	for (t = list; t; t = t->next) {
		tstart = t->l.l_start;
		if (t->l.l_len == 0)
			tend = 0xffffffffL;
		else
			tend = tstart + t->l.l_len - 1;

	/* look for overlapping locks */
		if (tstart <= lstart && tend >= lstart && t->l.l_pid != pid &&
		    (ltype == F_WRLCK || t->l.l_type == F_WRLCK))
			break;
		if (lstart <= tstart && lend >= tstart && t->l.l_pid != pid &&
		    (ltype == F_WRLCK || t->l.l_type == F_WRLCK))
			break;
	}
	return t;
}

/*
 * check to see that a file is a directory, and that write permission
 * is granted; return an error code, or 0 if everything is ok.
 */
long
dir_access(dir, perm, mode)
	fcookie *dir;
	unsigned perm;
	unsigned *mode;
{
	XATTR xattr;
	long r;

	r = (*dir->fs->getxattr)(dir, &xattr);
	if (r) {
		DEBUG(("dir_access: file system returned %ld", r));
		return r;
	}
	if ( (xattr.mode & S_IFMT) != S_IFDIR ) {
		DEBUG(("file is not a directory"));
		return EPTHNF;
	}
	if (denyaccess(&xattr, perm)) {
		DEBUG(("no permission for directory"));
		return EACCDN;
	}
	*mode = xattr.mode;
	return 0;
}

/*
 * returns 1 if the given name contains a wildcard character 
 */

int
has_wild(name)
	const char *name;
{
	char c;

	while ((c = *name++) != 0) {
		if (c == '*' || c == '?') return 1;
	}
	return 0;
}

/*
 * void copy8_3(dest, src): convert a file name (src) into DOS 8.3 format
 * (in dest). Note the following things:
 * if a field has less than the required number of characters, it is
 * padded with blanks
 * a '*' means to pad the rest of the field with '?' characters
 * special things to watch for:
 *	"." and ".." are more or less left alone
 *	"*.*" is recognized as a special pattern, for which dest is set
 *	to just "*"
 * Long names are truncated. Any extensions after the first one are
 * ignored, i.e. foo.bar.c -> foo.bar, foo.c.bar->foo.c.
 */

void
copy8_3(dest, src)
	char *dest;
	const char *src;
{
	char fill = ' ', c;
	int i;

	if (src[0] == '.') {
		if (src[1] == 0) {
			strcpy(dest, ".       .   ");
			return;
		}
		if (src[1] == '.' && src[2] == 0) {
			strcpy(dest, "..      .   ");
			return;
		}
	}
	if (src[0] == '*' && src[1] == '.' && src[2] == '*' && src[3] == 0) {
		dest[0] = '*';
		dest[1] = 0;
		return;
	}

	for (i = 0; i < 8; i++) {
		c = *src++;
		if (!c || c == '.') break;
		if (c == '*') {
			fill = c = '?';
		}
		*dest++ = toupper(c);
	}
	while (i++ < 8) {
		*dest++ = fill;
	}
	*dest++ = '.';
	i = 0;
	fill = ' ';
	while (c && c != '.')
		c = *src++;

	if (c) {
		for( ;i < 3; i++) {
			c = *src++;
			if (!c || c == '.') break;
			if (c == '*')
				c = fill = '?';
			*dest++ = toupper(c);
		}
	}
	while (i++ < 3)
		*dest++ = fill;
	*dest = 0;
}

/*
 * int pat_match(name, patrn): returns 1 if "name" matches the template in
 * "patrn", 0 if not. "patrn" is assumed to have been expanded in 8.3
 * format by copy8_3; "name" need not be. Any '?' characters in patrn
 * will match any character in name. Note that if "patrn" has a '*' as
 * the first character, it will always match; this will happen only if
 * the original pattern (before copy8_3 was applied) was "*.*".
 *
 * BUGS: acts a lot like the silly TOS pattern matcher.
 */

int
pat_match(name, template)
	const char *name, *template;
{
	register char *s, c;
	char expname[TOS_NAMELEN+1];

	if (*template == '*') return 1;
	copy8_3(expname, name);

	s = expname;
	while ((c = *template++) != 0) {
		if (c != *s && c != '?')
			return 0;
		s++;
	}
	return 1;
}

/*
 * int samefile(fcookie *a, fcookie *b): returns 1 if the two cookies
 * refer to the same file or directory, 0 otherwise
 */

int
samefile(a, b)
	fcookie *a, *b;
{
	if (a->fs == b->fs && a->dev == b->dev && a->index == b->index)
		return 1;
	return 0;
}
