/* This file is part of 'minixfs' Copyright 1991,1992,1993,1994 S.N.Henson */

#include "minixfs.h"
#include "proto.h"
#include "global.h"

FILESYS minix_filesys = {
	(FILESYS *) 0,
#ifdef SYSUPDATE
	FS_CASESENSITIVE | FS_LONGPATH | FS_DO_SYNC,
#else
	FS_CASESENSITIVE | FS_LONGPATH,
#endif
	m_root, m_lookup, m_creat, m_getdev,
	m_getxattr, m_chattr, m_chown,
	m_chmode, m_mkdir,
	m_rmdir, m_remove, m_getname,
	m_rename, m_opendir, m_readdir,
	m_rewinddir, m_closedir,
	m_pathconf, m_dfree,
	m_wlabel, m_rlabel,
	m_symlink, m_readlink,
	m_hardlink, m_fscntl, m_dskchng,
	m_release,m_dupcookie,
#ifdef SYSUPDATE
	l_sync
#endif
};

extern DEVDRV minix_dev;

static restore_dev=-1;

/*
 * the kernel calls this when it detects a disk change. Note the mnt_flags
 * flag MNT_CHANGE signals that the drive is being changed to force relogging
 * of it's root directory and it should not be completely changed.
 */

/*
 * Hack alert: disk changes on mounted filesystems are ungracefully handled.
 * Basically, if it isn't the first forced change then they are ignored. See
 * comment (3) in #if does_not_work_yet for reason. This isn't any great loss
 * because changing mounted filesystems is very naughty (causing various Unix
 * variants to panic, in more senses than one. If Unix doesn't handle it why
 * should I? ). Actually since changes, by necessity force the mounted 
 * structure to be destroyed, there isn't much we can gracefully do.
 */

long m_dskchng(d)
int d;
{
	FILEPTR *f, **last;
	super_info *psblk;
	char ignore;
	psblk=super_ptr[d];

	TRACE("Disk Change drive %c",d+'A');

	if(psblk && psblk!=DFS)
	{
		ignore = psblk->mnt_flags & MNT_CHANGE;
		/* If mounted then (for now) ignore changes */
		if(!ignore && psblk->mnt_inode) return 0;
	}
	else ignore=0;

	if(psblk && psblk!=DFS && !ignore)
	{
		Kfree(psblk->ibitmap);

#if does_not_work_yet

		/* Need to do three things:
		 * 1. Make sure all children are no longer mounted.
		 * 2. Make sure filesystem is no longer searched in lookups
		 *    of parent device.
		 * 3. (sigh) Force media change of all descendents without
		 *    disturbing mounted structure, so MiNT will reread the
		 *    root dirs properly.
		 */

		if(psblk->mnt_inode)
		/* Umount this filesystem */
		{
		  super_info **ublk,*tsblk;
		  /* Unlink from list on parent device */
		  for(ublk=&super_ptr[psblk->mnt_dev]->mnt_first;*ublk;
						      ublk=&(*ublk)->mnt_next)
		    if(*ublk==psblk)
		    {
			*ublk=psblk->mnt_next;
			break;
		    }
		    /* Umount all children */
		    for(tsblk=psblk->mnt_first;tsblk;tsblk=tsblk->mnt_next)
							     tsblk->mnt_inode=0;
		}
#endif
		Kfree(psblk);
	}
	if(!ignore) super_ptr[d]=0;

	/* this may affect the m_getname cache, too */
	if (lpath && (d == lroot.dev || d == ldir.dev)) {
		Kfree(lpath);
		lpath = 0;
	}

	/* Since the disk has changed always invalidate cache */
	m_invalidate(d);

	/* Free any memory associated to file pointers of this drive. */
	last = &firstptr;
	for (f = *last; f != 0; f = *last)
	  {
	    if (f->fc.dev == d)
	      {
		f_cache *fch = (f_cache *) f->devinfo;
		/* The lock structure is shared between the fileptr's.
		   Make sure that it is freed only once. */
		if (!f->next || f->next->fc.dev != d
		    || f->next->fc.index != f->fc.index)
		  {
		    LOCK *lck, *nextlck;
		    nextlck = *fch->lfirst;
		    while ((lck = nextlck) != 0)
		      {
			nextlck = lck->next;
			Kfree (lck);
		      }
		    Kfree (fch->lfirst);
		  }
		Kfree (fch);
		/* Remove this fileptr from the list. */
		*last = f->next;
		f->next = 0;
	      }
	    else
	      last = &f->next;
	  }
	    
	if(!ignore) minix_sanity(d);

	return 1;
}

/*
 * Note: in the first round of initialisations, we assume that floppy
 * drives (A and B) don't belong to us; but in a later disk change,
 * they may very well be ours, so we remember that. This is means that a
 * minix disk inserted into a drive will be unrecognisable at boot up and
 * a forced disk change is needed. However for MiNT 1.05 (and presumably
 * later) drives are initialised on first access so this isn't needed.
 */

long m_root(dev,dir)
int dev;
fcookie *dir;
{
	int ret;
	static first_init = 2;
	extern FILESYS dummy_filesys;
	super_info **psblk;

	psblk = super_ptr+dev;

	ret=0;

	if( (kernel->maj_version==0 ) || 
		(kernel->maj_version==1 && kernel->min_version < 5 ) ) {
		/* the first 2 checks (on A: and B:) we fail automatically */
		if (first_init ) {
			--first_init;
			return -1;
		}
	}

	/* If not present, see if it's valid */
	if( *psblk || ( dev >= 0 && (ret=minix_sanity(dev)) ) ) {
		if(ret ==-1 || *psblk==DFS ) dir->fs = &dummy_filesys;
		else
		{
			dir->fs=&minix_filesys;
			/* Aux field tells original device */
			dir->aux= dev | AUX_DRV ;
			dir->index=ROOT_INODE;
			/* If mounted trace back to root filesystem */
			while((*psblk)->mnt_inode)
			{
				dev=(*psblk)->mnt_dev;
				psblk=super_ptr+dev;
			}
		}
		dir->dev=dev;
		return 0;

	}
	return -1;
}

long m_lookup(dir,name,entry)
fcookie *dir; 
char *name;
fcookie *entry;
{

	if(!*name)
	{
		*entry=*dir;
		entry->aux=0;
		return 0;
	}

	if(dir->index==ROOT_INODE && !strcmp(name,".."))
	{
		*entry=*dir;
		/* If mounted treat as a lookup from mount point */
		if( cross_mount(entry) ) return EMOUNT;
		DEBUG("m_lookup: crossing mount point");
		entry->index = search_dir(name,entry->index,entry->dev,FIND);
		if(entry->index < 0 )  return entry->index;
		entry->aux=0;
		return 0;
	}

	entry->index=search_dir(name,dir->index,dir->dev,FIND);
	entry->dev=dir->dev;
	if(entry->index < 0 ) return entry->index ;
	entry->aux=0;
	entry->fs=&minix_filesys;
	if( check_mount(entry) ) DEBUG("Crossed mount point");
	return 0;
}

long m_creat(dir,name,mode,attr,entry)
fcookie *dir;
char *name;
unsigned mode;
int attr;
fcookie *entry;
{
	long pos;
	d_inode ripnew;
	unshort newfile;
	char *ext;
	
	/* Create dir entry */	
	if((pos=search_dir(name,dir->index,dir->dev,ADD))<0)
	{	
		return pos;
	}

	/* Get new inode */
	if(!(newfile=alloc_inode(dir->dev)))
	{
		DEBUG("m_getdev: no free inodes");
		return EWRITF;
	}
	/* Set up inode */
	bzero(&ripnew,sizeof(d_inode));

/* If  creating a file with approriate extensions 
 * automatically give it execute permissions.
 */
	if(do_trans(AEXEC_TOS,dir->dev) && ( ext=strrchr(name,'.') ) )
	{
		ext++;
		if( 
		/* Insert your favourite extensions here */
		  !(  Stricmp(ext,"TTP") && Stricmp(ext,"PRG") 
		   && Stricmp(ext,"APP") && Stricmp(ext,"TOS") 
		   && Stricmp(ext,"ACC") && Stricmp(ext, "GTP")))
				mode |= 0111;
	}
	ripnew.i_mode= I_REGULAR | mode;
	ripnew.i_uid=Geteuid();
	ripnew.i_gid=Getegid();
	ripnew.i_nlinks=1;

	ripnew.i_mtime=Unixtime(Timestamp(), Datestamp());
	ripnew.i_atime=ripnew.i_mtime;
	ripnew.i_ctime=ripnew.i_mtime;

	write_inode(newfile,&ripnew,dir->dev);
	l_write(dir->index,pos,2L,&newfile,dir->dev);

	if(cache_mode) l_sync();

	entry->fs = dir->fs;
	entry->dev = dir->dev;
	entry->index=newfile;
	entry->aux=0;
	return 0;
}

DEVDRV * m_getdev(file,special)
fcookie *file;
long *special;
{
	return(&minix_dev);
}

long m_getxattr(file,xattr)
fcookie *file;
XATTR *xattr;
{
        d_inode rip;
	long time_tmp;
	long extra;
	super_info *psblk;
	psblk=super_ptr[file->dev];
	read_inode(file->index,&rip,file->dev);
	/* Minix and gcc use different values for FIFO's */
	if((rip.i_mode & I_TYPE) == I_NAMED_PIPE)
		xattr->mode = S_IFIFO | (rip.i_mode & ALL_MODES);
	else xattr->mode=rip.i_mode;

	/* We could potentially have trouble with symlinks too */
#if I_SYMLINK != S_IFLNK
	if( (rip.i_mode & I_TYPE) == I_SYMLINK)
		xattr->mode = S_IFLNK | (rip.i_mode & ALL_MODES);
#endif

	/* Fake attr field a bit , to keep TOS happy */
	if(IS_DIR(rip))xattr->attr=FA_DIR;
	else xattr->attr=(rip.i_mode & 0222) ? 0 : FA_RDONLY;

        xattr->index=file->index;
        xattr->dev=file->dev;

	/* Char and block special files need major/minor device nos filled in */
	if(IM_SPEC(rip.i_mode)) xattr->rdev=rip.i_zone[0];
        else xattr->rdev=0;

        xattr->nlink=rip.i_nlinks;
        xattr->uid=rip.i_uid;
        xattr->gid=rip.i_gid;
        xattr->size=rip.i_size;
	xattr->blksize = BLOCK_SIZE;
/* Note: the nblocks calculation is accurate only if the file is
 * contiguous. It usually will be, and if it's not, it shouldn't
 * matter ('du' will return values that are slightly too high)
 */
	xattr->nblocks = (xattr->size + (BLOCK_SIZE-1)) / BLOCK_SIZE;
	extra = 0;
	if (xattr->nblocks > psblk->dzpi)
		extra++;	/* correct for the indirection block */
	if (xattr->nblocks > psblk->ndbl) {
		extra++;	/* correct for double indirection block */
		extra += (xattr->nblocks - psblk->ndbl) / psblk->zpind;
				/* and single indirection blocks */
	}
	if (xattr->nblocks > psblk->ndbl + (long) psblk->zpind * psblk->zpind)
	  {
	    extra++;	/* correct for triple indir block */
	    /* and double indirection blocks */
	    extra += ((xattr->nblocks - psblk->ndbl
		       - (long) psblk->zpind * psblk->zpind)
		      / ((long) psblk->zpind * psblk->zpind));
	  }
	xattr->nblocks += extra;

	time_tmp=Dostime(_corr(rip.i_mtime));
	xattr->mtime=time_tmp >> 16;
	xattr->mdate=time_tmp & (0xffff);
	time_tmp=Dostime(_corr(rip.i_atime));
	xattr->atime=time_tmp >> 16;
	xattr->adate=time_tmp & (0xffff);
	time_tmp=Dostime(_corr(rip.i_ctime));
	xattr->ctime=time_tmp >> 16;
	xattr->cdate=time_tmp & (0xffff);

	xattr->reserved2=0;
	xattr->reserved3[0]=0;
	xattr->reserved3[1]=0;

	return 0;
}

long m_chown(file, uid , gid)
fcookie *file;
int uid,gid;
{

	d_inode rip;
	read_inode(file->index,&rip,file->dev);
 	if(uid!=-1)rip.i_uid=uid;
	if(gid!=-1)rip.i_gid=gid;
	rip.i_ctime=Unixtime(Timestamp(),Datestamp());
	write_inode(file->index,&rip,file->dev);
	if(cache_mode) l_sync();
	return 0;
}

long m_chmode(file, mode)
fcookie *file;
unsigned mode;
{
        d_inode rip;
	super_info *psblk=super_ptr[file->dev];

        read_inode(file->index,&rip,file->dev);

        rip.i_mode=(rip.i_mode & I_TYPE)|(mode & ALL_MODES);                
	if(psblk->version)rip.i_ctime=Unixtime(Timestamp(),Datestamp());
	write_inode(file->index,&rip,file->dev);
	if(cache_mode) l_sync();
        return 0;
}


long m_mkdir(dir,name,mode)
fcookie *dir;
char *name;
unsigned mode;
{
	unshort newdir;
	d_inode rip,ripnew;
	long pos;
	int incr;
	dir_struct blank[MAX_INCREMENT*2];
	incr=super_ptr[dir->dev]->increment;
	if((pos=search_dir(name,dir->index,dir->dev,ADD))<0)return pos;
	read_inode(dir->index,&rip,dir->dev);
	if(rip.i_nlinks>=MINIX_MAX_LINK)return EACCDN;
	/* Get new inode */
	if(!(newdir=alloc_inode(dir->dev)))return EACCDN;

	/* Set up inode */
	bzero(&ripnew,sizeof(d_inode));
	ripnew.i_mode=I_DIRECTORY | (mode & 0777);
	ripnew.i_uid=Geteuid();
	ripnew.i_gid=Getegid();
	ripnew.i_nlinks=2;
	ripnew.i_mtime=Unixtime(Timestamp(), Datestamp());
	ripnew.i_ctime=ripnew.i_mtime;
	ripnew.i_atime=ripnew.i_mtime;
	write_inode(newdir,&ripnew,dir->dev);

	/* Set up new directory */
	strcpy(blank[0].d_name,".");
	blank[0].d_inum=newdir;
	strcpy(blank[incr].d_name,"..");
	blank[incr].d_inum=dir->index;
	if(l_write((unsigned)newdir,-1L,(long)(DIR_ENTRY_SIZE*2*incr),
		blank,dir->dev)!=(incr*DIR_ENTRY_SIZE*2) )
	{
		ripnew.i_mode=0;
		ripnew.i_nlinks=0;
		write_inode(newdir,&ripnew,dir->dev);
		free_inode(newdir,dir->dev);
		if(cache_mode) l_sync();
		return EACCDN;
	}
	rip.i_nlinks++;
	write_inode(dir->index,&rip,dir->dev);
	l_write(dir->index,pos,2L,&newdir,dir->dev);

	if(cache_mode) l_sync();

	return(0);
}

long m_rmdir(dir,name)
fcookie *dir;
char *name;
{
	long chunk,left;
	long inum;
	int i,incr;
	super_info *psblk;
	d_inode rip,rip2;
	if((inum=search_dir(name,dir->index,dir->dev,FIND))<0)return inum;

	/* Is anything mounted on this dir ? */
	for(psblk=super_ptr[dir->dev]->mnt_first;psblk;psblk=psblk->mnt_next)
		if(psblk->mnt_inode==inum)
		{
			DEBUG("m_rmdir: can't delete mount point");
			return EACCDN;
		}

	read_inode(inum,&rip,dir->dev);
	read_inode(dir->index,&rip2,dir->dev);
	if(!IS_DIR(rip))return EFILNF;
	incr=super_ptr[dir->dev]->increment;
	/* Check if dir is actually empty */
	for(chunk=0;(left=next_zone(&rip,chunk,&temp,dir->dev)/DIR_ENTRY_SIZE);
		chunk++)
	{
		for(i=0;i<left;i+=incr)
		   if (temp.bdir[i].d_inum
		       && (temp.bdir[i].d_name[0] != '.'
			   || temp.bdir[i].d_name[1] != 0)
		       && (temp.bdir[i].d_name[0] != '.'
			   || temp.bdir[i].d_name[1] != '.'
			   || temp.bdir[i].d_name[2] != 0))
			return EACCDN ;
	}
	if(!inode_busy(inum,dir->dev,1))
	{
		trunc_inode(&rip,dir->dev,0L,0);
		rip.i_mode=0;
		free_inode(inum,dir->dev);
	}
	rip.i_nlinks=0;
	write_inode(inum,&rip,dir->dev);
	read_inode(dir->index,&rip,dir->dev);
	rip.i_mtime=Unixtime(Timestamp(), Datestamp());
	rip.i_nlinks--;
	write_inode(dir->index,&rip,dir->dev);
	search_dir(name,dir->index,dir->dev,KILL);

	if( lpath && (ldir.dev==dir->dev) && (ldir.index==inum) )
	{
		Kfree(lpath);
		lpath=0;
	}

	if(cache_mode) l_sync();

	return(0);
}

/* Unix-like unlink ... handle regulars, symlinks and specials.
 * 
 */

long m_remove(dir,name)
fcookie *dir;
char *name;
{
	long inum,ret;
	char spec;	/* Special file */
	d_inode rip;
	inum=search_dir(name,dir->index,dir->dev,FIND);
	if(inum<0) return inum;
	read_inode(inum,&rip,dir->dev);
	if(!IS_REG(rip) && !IS_SYM(rip) ) 
	{
		if(!IM_SPEC(rip.i_mode)) return EACCDN;
		spec=1;
	}
	else spec=0;
	if((ret=search_dir(name,dir->index,dir->dev,KILL))<0) return ret;	
	if(--rip.i_nlinks==0)
	{
		if(spec || !inode_busy(inum,dir->dev,1)) /* Is inode busy ? */
		{
			if(!spec) trunc_inode(&rip,dir->dev,0L,0);
			rip.i_mode=0;
			free_inode(inum,dir->dev);
		}
	}
	write_inode(inum,&rip,dir->dev);

	if(cache_mode) l_sync();

	return(0);
}

/* This function is inefficient, it uses the standard sys V method of 
 * finding out the pathname of the cwd : for each part of the path, search
 * the parent for a link with the same inode number as '..' , append this to the
 * path until we get to root dir, then reverse order of dirs. This way no 
 * temporary buffers are allocated which could overflow or kmalloc to fail ...
 */

/* In fact its so inefficient a mini-cache remembers the last call info */

long m_getname(root,dir,pathname,length)
fcookie *root,*dir;
char *pathname;
short length;
{
	long inum,pinum;
	unsigned dev;
	int chunk;
	long left;
	int incr;
	short plength;
	super_info *psblk;
	psblk=super_ptr[dir->dev];

	if(no_length) length=PATH_MAX;

	if(lpath && lroot.dev==root->dev && 
		lroot.index==root->index && ldir.dev==dir->dev && 
		ldir.index==dir->index)
	{
		TRACE("m_getname: cache hit");
		if(length <= llength) return ENAMETOOLONG;
		strcpy(pathname,lpath);
		return 0;
	}

	*pathname=0;

	if( dir->dev==root->dev && dir->index==root->index) return 0;

	incr=psblk->increment;
	inum=dir->index;
	dev=dir->dev;
	plength=0;
	if(inum==ROOT_INODE && psblk->mnt_inode)
	{
		dev=psblk->mnt_dev;
		inum=psblk->mnt_inode;
		psblk=super_ptr[dev];
	}

	while( inum!=root->index && inum!= ROOT_INODE )
	{
		d_inode rip;
		cache *tmp;
		pinum=search_dir("..",inum,dev,FIND); 
		/* Parent inum */

		if(pinum < 0) /* If this happens we're in trouble */
		{
			ALERT("No .. in inode %d , drive %c",inum,dir->dev+'A');
			return pinum;
		}
		read_inode(pinum,&rip,dev);
		for(chunk=0;
		(left=cnext_zone(&rip,chunk,&tmp,dev)/DIR_ENTRY_SIZE) &&
		inum!=pinum ;chunk++)
		{
			char tname[MNAME_MAX+1];
			int i;
			for(i=0;i<left && inum!=pinum ;i+=incr)
			if(tmp->buffer->bdir[i].d_inum==inum)
			{
				strncpy(tname,tmp->buffer->bdir[i].d_name,MMAX_FNAME(incr));
				tname[MMAX_FNAME(incr)]=0;
				strrev(tname);
				plength+=strlen(tname)+1;
				if(length <= plength) return ENAMETOOLONG;
				strcat(pathname,tname);
				strcat(pathname,"\\");
				inum=pinum;
			}
		}
		if(left==0 && inum!=pinum) {
			ALERT("m_getname inode %d orphaned or bad ..",inum);
			return EINTRN;
		}
		/* Cross mount point if possible */
		if(inum==ROOT_INODE && psblk->mnt_inode)
		{
			dev=psblk->mnt_dev;
			inum=psblk->mnt_inode;
			psblk=super_ptr[dev];
		}
	}
	if(inum==ROOT_INODE && root->index!=ROOT_INODE)
	{
		DEBUG("m_getname: Hmmmm root is not a parent of dir");
		return EINTRN;
	}
	strrev(pathname);
	if(lpath)Kfree(lpath);
	if( (lpath=Kmalloc(strlen(pathname)+1)) )
	{
		strcpy(lpath,pathname);
		llength=plength;
	}

	lroot=*root;
	ldir=*dir;
	return 0;
}


long m_opendir(dirh,flag)
DIR *dirh;
int flag;
{
	dirh->index=0;
	return 0;
}

long m_readdir(dirh,name,namelen,fc)
DIR *dirh;
char *name;
int namelen;
fcookie *fc;
{
        d_inode rip;
	cache *tmp;
	unsigned entry,chunk;
	super_info *psblk;
	long limit;
	int flag,incr;
	psblk=super_ptr[dirh->fc.dev];
	if(dirh->flags) flag=do_trans(DIR_TOS,dirh->fc.dev);
	else flag=0;
	if(!dirh->fc.index)return EACCDN;
	entry=dirh->index % NR_DIR_ENTRIES;
	chunk=dirh->index / NR_DIR_ENTRIES;	
	read_inode(dirh->fc.index,&rip,dirh->fc.dev);
	incr=psblk->increment;

	while( (limit=cnext_zone(&rip,chunk,&tmp,dirh->fc.dev)/DIR_ENTRY_SIZE) )
	{
		while( entry < limit)
	  	{
			dir_struct *try=&tmp->buffer->bdir[entry];
			entry+=incr;
			if(try->d_inum)
			{
				char *tmpnam;
				tmpnam=tosify(try->d_name,flag,MMAX_FNAME(incr));

				if (dirh->flags==0)
				{
					namelen -= sizeof(long);
					if (namelen <= 0) return ERANGE;
					*((long *)name) = (long)try->d_inum;
					name += sizeof(long);
	       			}

				strncpy(name,tmpnam,namelen);
				dirh->index=entry+chunk*NR_DIR_ENTRIES;
			/* set up a file cookie for this entry */
				fc->dev = dirh->fc.dev;
				fc->aux = 0;
				fc->index = (long)try->d_inum;
				fc->fs = &minix_filesys;
				if(strlen(tmpnam) >= namelen) 
					return ENAMETOOLONG;
				/* If turbo mode set atime here: we'll only
				 * change the cache here so it wont cause
				 * lots of I/O
				 */
				if( cache_mode==TURBO
					&& super_ptr[dirh->fc.dev]->version
					&& dirh->fc.dev > 1 )
							   set_atime(&dirh->fc);
				return 0;
			}
		}
		if(entry!=NR_DIR_ENTRIES)return ENMFIL;
		else entry=0;
		chunk++;
	}
	return ENMFIL;
}

long m_rewinddir(dirh)
DIR *dirh;
{
	dirh->index=0;
	return 0;
}

long m_closedir(dirh)
DIR *dirh;
{

/* Access time is set here if we aren't in TURBO cache mode. Otherwise we
 * would be sync'ing on every dir read which would be far too slow. See note
 * in set_atime().
 */

	if( cache_mode!=TURBO && super_ptr[dirh->fc.dev]->version && 
			dirh->fc.dev > 1 )
	{
		set_atime(&dirh->fc);
		l_sync();
	}

	dirh->fc.index=0;
	return 0;
}

/* Set the atime of a V2 inode for filesystems. There is a snag here: if the
 * disk is changed then this is likely not to be written out before the whole
 * cache is invalidated. So we set the status to '3' which means that it is
 * not alerted if this is dirty when invalidated (hardly the end of the world
 * if the atime is slightly wrong!)
 */
void set_atime(fc)
fcookie *fc;
{
	d_inode *rip;
	int *status;
	rip=get_inode2(fc->index,fc->dev,&status,NOGUESS);
	rip->i_atime=Unixtime(Timestamp(),Datestamp());
	if(*status!=2) *status=3;
}


long m_rlabel(dir,name,namelen)
fcookie *dir;
char *name;
int namelen;
{
	return EFILNF;
}

long m_wlabel(dir,name)
fcookie *dir;
char *name;
{
	return EACCDN;
}

long m_dfree(dir,buffer)
fcookie *dir;
long *buffer;
{
	super_info *psblk ;
	psblk = super_ptr[dir->dev];
	buffer[1] = psblk->sblk.s_zones-psblk->sblk.s_firstdatazn;
	buffer[0] = buffer[1] - count_bits(psblk->zbitmap,buffer[1]+1)+1;
	buffer[2]=512L;
	buffer[3]=2L;
	return(0);
}

long m_fscntl(dir,name,cmd,arg)
fcookie *dir;
char *name;
int cmd;
long arg;
{
	FILEPTR *f;
	mfs_info *inf;
	super_info *psblk;
	long inum;
	int uid,gid,id;
	d_inode rip;
	extern long init_addr;

	uid = Geteuid();
	gid = Getegid();

	switch(cmd)
	{
		case MFS_VERIFY:
		*((long *)arg)=MFS_MAGIC;
		return 0;

		/* Sync the filesystem */
		case MFS_SYNC:
		TRACE("Done l_sync()");
		l_sync();
		return 0;

		/* Invalidate all cache entries for a given drive */
		case MFS_CINVALID:
		if(uid) return EACCDN;
		m_invalidate(dir->dev);
		return 0;

		/* Invalidate all fileptrs for a given drive */
		case MFS_FINVALID:
		if(uid) return EACCDN;
		id=Getpid();
		for(f=firstptr;f;f=f->next)if(f->fc.dev==dir->dev)m_close(f,id);
		return 0;

		case MFS_INFO:
		psblk=super_ptr[dir->dev];
		inf=(mfs_info *)arg;
		inf->total_zones=psblk->sblk.s_zones-psblk->sblk.s_firstdatazn;
		inf->total_inodes=psblk->sblk.s_ninodes;
		inf->version=psblk->version+1;
		inf->increment=psblk->increment;		
		inf->free_inodes=inf->total_inodes-
			count_bits(psblk->ibitmap,inf->total_inodes+1)+1;
		inf->free_zones=inf->total_zones-count_bits(psblk->zbitmap,inf->total_zones+1)+1;
		return 0;

		case MFS_IMODE:
		if(uid) return EACCDN;
		inum=search_dir(name,dir->index,dir->dev,FIND);
		if(inum < 0 ) return inum;
		read_inode(inum,&rip,dir->dev);
		rip.i_mode=arg;
		write_inode(inum,&rip,dir->dev);
		return 0;

		case MFS_GTRANS:
		*((long *) arg)=fs_mode[dir->dev];
		return 0;

		case MFS_STRANS:
		if(uid) return EACCDN;
		fs_mode[dir->dev]=*((long *)arg);
		return 0;

		case MFS_PHYS:
		*((struct phys_part *)arg)=ppart[dir->dev];
		return 0;

		case MFS_IADDR:
		*((long *)arg)=(long)&init_addr;
		return 0;

		case MFS_UPDATE:
		if(cache_mode!=TURBO ) return -1;
		switch(arg)
		{
			case 0:
			return update_suspend;

			case 1:
			TRACE("Minixfs: update suspended");
			update_suspend=1;
			return 0;

			case 2:
			TRACE("Minixfs: update restarted");
			update_suspend=0;
			return 0;

			case 3:
			if(Addroottimeout) return -1;
			return update_pid;

			default:
			return EINVFN;
		}

/* Mounting and umounting.
 * This basically involves cookie translation when crossing the mount point.
 * However this presents an interesting problem. Suppose E:\usr has D: mounted
 * on it. If we are in directory E:\usr\ we are in the root directory of D:.
 * If we do a cd \ then we get sent back to the root cookie directory of D:,
 * which we want to be E:. The only way to change the root cookie at present is
 * to force a disk change, this is no problem.
 * The fun starts when we want to umount D:. Changing D: will have no effect
 * because the root cookie of D: is now on E: and since E: hasn't changed the
 * root cookie on D: wont change either. If we change E: then we will get the
 * root cookie of D: changed but we also change E: as well.
 * The only way out is to fiddle the aux field of the root cookie so it shows
 * the original device. The cookie from m_root is copied with the dup_cookie
 * function, if we kludge this at umount time to read the aux field and copy
 * the original device the root cookies should be set back. This relies very
 * heavily on the structure of filesys.c in MiNT, but it's better than nothing.
 * Repeat after me: KLUDGE, KLUDGE, KLUDGE, KLUDGE, KLUDGE!!!
 */

		case MFS_MOUNT:
		{
			char tpath[]="A:";
			fcookie fc;
			unsigned dev;
			super_info *mptr;
			if(uid) return EACCDN;
			/* Lookup the entry */
			if( (inum=m_lookup(dir,name,&fc)) ) return inum;
			read_inode(fc.index,&rip,fc.dev);
			/* Must be a directory */
			if(!IS_DIR(rip))
			{
				DEBUG("MFS_MOUNT: not a directory!");
				return EACCDN;
			}

			if(fc.index==ROOT_INODE)
			{
				DEBUG("MFS_MOUNT: can't mount on root!");
				return EACCDN;
			}
			if(!arg) return EINVFN;
			dev = ( ( mfs_mount *) arg)->dev ;

			if(fc.dev==dev)
			{
				DEBUG("MFS_MOUNT: can't mount on self!");
				return EACCDN;
			}

			tpath[0]='A'+dev;

			/* Sync filesystem (and force access if unrecognised) */

			d_cntl(MFS_SYNC,tpath,0l);

			psblk=super_ptr[dev];

			if(!psblk || psblk==DFS)
			{
				DEBUG("MFS_MOUNT: not a Minixfs filesystem!");
				return EACCDN;
			}

			if(psblk->mnt_inode)
			{
			  DEBUG("MFS_MOUNT: filesystem already mounted!");
			  return EACCDN;
			}

			if(psblk->mnt_first)
			{
			  DEBUG("MFS_MOUNT: filesystem has others mounted!");
			  return EACCDN;
			}

			for(mptr=super_ptr[fc.dev]->mnt_first;
						  mptr;mptr=mptr->mnt_next)
			{
			  if( mptr->mnt_inode==fc.index )
			  {
			    DEBUG("MFS_MOUNT: inode already mounted on!");
			    return EACCDN;
			  }
			}

			mptr=super_ptr[fc.dev];

			psblk->mnt_next=mptr->mnt_first;
			mptr->mnt_first=psblk;

			psblk->mnt_dev=fc.dev;
			psblk->mnt_inode=fc.index;

			psblk->mnt_flags |= MNT_CHANGE;

			/* Force change */
			d_lock(1,dev);
			d_lock(0,dev);

			d_cntl(MFS_SYNC,tpath,0l);

			psblk->mnt_flags &= ~MNT_CHANGE;

			return 0;
		}

		case MFS_UMOUNT:
		{
			fcookie fc;
			super_info **pptr,*pdev;
			char tpath[]="A:";

			if(uid) return EACCDN;
			/* Lookup path */
			if( ( inum=m_lookup(dir,name,&fc) ) ) return inum;

			psblk=super_ptr[fc.dev];

			/* Can't have other devices mounted */
			if(psblk->mnt_first)
			{
				DEBUG("MFS_UMOUNT: device busy");
				return EACCDN;
			}

			if(!psblk->mnt_inode)
			{
				DEBUG("MFS_UMOUNT: not mounted");
				return EACCDN;
			}

			/* Unlink from parent filesystem list */
			for(pptr=&super_ptr[psblk->mnt_dev]->mnt_first;*pptr;
						      pptr=&(*pptr)->mnt_next )
			{
				if(*pptr==psblk)
				{
					*pptr = psblk->mnt_next;
					break;
				}
			}

			pdev=psblk;

			/* Find root device */
			while(pdev->mnt_inode) pdev = super_ptr[pdev->mnt_dev];

			psblk->mnt_inode=0;

			pdev->mnt_flags |= MNT_CHANGE;

			/* Make dupcookie restore dev fields */
			restore_dev = psblk->dev;

			/* Change root device */
			d_lock(1,pdev->dev);
			d_lock(0,pdev->dev);

			tpath[0] = 'A'+pdev->dev;
			d_cntl(MFS_SYNC,tpath,0l);

			restore_dev = -1;

			pdev->mnt_flags &= ~MNT_CHANGE;

			return 0;

		}

		case FUTIME:
		case FTRUNCATE:
		{
			fcookie fc;
			read_inode(dir->index,&rip,dir->dev);
		/* Have we got 'x' access for current dir ? */
 		if (check_mode(uid,gid,&rip,S_IXUSR)) 
		  return EACCDN;
		/* Lookup the entry */
			if( (inum=m_lookup(dir,name,&fc)) ) return inum;
			read_inode(fc.index,&rip,fc.dev);
			if(cmd==FUTIME)
			{
	  		  short *timeptr = (short *) arg;
			  /* The owner or super-user can always touch,
			     others only if timeptr == 0 and write
			     permission. */
			  if (uid && uid != rip.i_uid
			      && (timeptr
				  || check_mode (uid, gid, &rip, S_IWUSR)))
			    return EACCDN;
				
			  rip.i_ctime = Unixtime(Timestamp (), Datestamp ());
			  if(timeptr)
			  {	
			    rip.i_atime = Unixtime(timeptr[0], timeptr[1]);
			    rip.i_mtime = Unixtime(timeptr[2], timeptr[3]);
			  }
			  else rip.i_atime = rip.i_mtime = rip.i_ctime;
				write_inode (fc.index, &rip, fc.dev);
				if (cache_mode != TURBO) l_sync();
				return 0;
			}

			if(!IS_REG(rip)) return EACCDN;
			/* Need write access as well */
			if (check_mode(uid, gid, &rip, S_IWUSR))
			  return EACCDN;
			itruncate(fc.index,fc.dev,*((long *)arg));
	  		if (cache_mode != TURBO) l_sync ();
			return 0;
		}

		case MFS_LOPEN:
		{
		  long fcount;
		  openf_list *flist = (openf_list *) arg;
		  fcount=0;
		  inum=0;
		  for(f=firstptr;f;f=f->next)
		  {
		    /* If same file or wrong device, skip */
		    if( f->fc.dev!=dir->dev || f->fc.index==inum) continue;
		    inum=f->fc.index;
		    flist->flist[fcount++]=inum;
		    if(fcount==flist->limit) return ERANGE;
		  }
		  flist->flist[fcount]=0;
		}
		return 0;

		case MFS_MKNOD:
		{
		 	long pos;
			unsigned inm,mode;

		 	if(uid) return EACCDN;
		 	mode = arg & 0xffff;

		 	/* Char and block specials only at present */
		 	if(!IM_SPEC(mode))return ERANGE;

		 	/* Create new name */
		 	pos=search_dir(name,dir->index,dir->dev,ADD);
		 	if(pos < 0) return pos;		 
		 	inm=alloc_inode(dir->dev);
		 	if(!inm) return EWRITF;

		 	bzero(&rip,sizeof(d_inode));

			rip.i_mode = mode;
			rip.i_uid = uid;
			rip.i_gid = gid;
			rip.i_nlinks = 1;

			rip.i_mtime=Unixtime(Timestamp(), Datestamp());
			rip.i_atime=rip.i_mtime;
			rip.i_ctime=rip.i_mtime;
			rip.i_zone[0]= arg >> 16;

			write_inode(inm,&rip,dir->dev);
			l_write(dir->index,pos,2L,&inm,dir->dev);
			if(cache_mode) l_sync();

		}
		return 0;

		default:
		return EINVFN;
	}
}

/* m_rename, move a file or directory. Directories need special attention 
 * because if /usr/foo is moved to /usr/foo/bar then the filesystem will be
 * damaged by making the /usr/foo directory inaccessible. The sanity checking
 * performed is very simple but should cover all cases: Start at the parent
 * of the destination , check if this is the source inode , if not then 
 * move back to '..' and check again , repeatedly check until the root inode
 * is reached , if the source is ever seen on the way back to the root then
 * the rename is invalid , otherwise it should be OK.
 */

long m_rename(olddir,oldname,newdir,newname)
fcookie *olddir;
char *oldname;
fcookie *newdir;
char *newname;
{
	long finode,ret;
	d_inode rip;
	long pos;
	char dirmove,dirren;
	dirmove=0;
	dirren=0;
/* Check cross drives */
	if(olddir->dev!=newdir->dev)return EXDEV;

/* Check new doesn't exist and path is otherwise valid */
	finode=search_dir(newname,newdir->index,newdir->dev,FIND);
	if(finode>0) return EACCDN;
	if(finode!=EFILNF) return finode;

/* Check old path OK */
	if((finode=search_dir(oldname,olddir->index,olddir->dev,FIND))<0)
		return finode;

	read_inode(finode,&rip,olddir->dev);

/* Sanity check movement of directories */
	if(IS_DIR(rip))
	{
		dirren=1;
	 	if(olddir->index!=newdir->index)
		{
#ifdef MFS_NMOVE_DIR
			return EACCDN;
#else
			d_inode riptemp;
			ret=is_parent(newdir->index,finode,olddir->dev);
			if(ret < 0) return ret;
			if(ret) return EACCDN;
			read_inode(newdir->index,&riptemp,newdir->dev);
			if(riptemp.i_nlinks==MINIX_MAX_LINK) return EACCDN;
			TRACE("minixfs: valid directory move");
			dirmove=1;
#endif
		}
	}

	/* Check the m_getname cache is not invalidated by this move ....
	if no dir move, invalidate if the ldir is the name being changed ,
	if we move dir's then if the olddir is a parent of ldir, invalidate */

	if (lpath && ldir.dev == olddir->dev
	    && (ldir.index == finode
		|| (dirmove && is_parent (ldir.index, finode, olddir->dev) > 0)))
	{
		Kfree (lpath);
		lpath=0;
	}
		
/* Create new entry */
      if((pos=search_dir(newname,newdir->index,newdir->dev,ADD))<0) return pos;
/* Delete old path */
	if((finode=search_dir(oldname,olddir->index,olddir->dev,KILL))<0)
			return finode;
	{
	  unshort ino = finode;
	  l_write (newdir->index, pos, 2L, &ino, newdir->dev);
	}

/* When moving directories, fixup things like '..' and nlinks of old and
 * new dirs
 */

	if(dirmove)
	{
		pos=search_dir("..",finode,newdir->dev,POS);
		if(pos<0) 
		{
			ALERT("m_rename: no .. in inode %ld",finode);
			return EACCDN;
		}
		if(pos!=DIR_ENTRY_SIZE*super_ptr[newdir->dev]->increment)
		  ALERT("m_rename: Unexpected .. position in inode %ld",finode);
		{
		  unshort ino = newdir->index;
		  l_write (finode, pos, 2L, &ino, newdir->dev);
		}
		read_inode(olddir->index,&rip,olddir->dev);
		rip.i_nlinks--;
		write_inode(olddir->index,&rip,olddir->dev);
		read_inode(newdir->index,&rip,newdir->dev);
		rip.i_nlinks++;
		write_inode(newdir->index,&rip,newdir->dev);
	}

	if(cache_mode) l_sync();

/* Check the m_getname cache is not invalidated by this move ....
 * if no dir alter, invalidate if the ldir is the name being changed ,
 * if we alter dir's then if the moved dir is a parent of ldir, invalidate.
 */

	if (lpath && ldir.dev == olddir->dev
	    && (ldir.index == finode
		|| (dirren && is_parent (ldir.index, finode, olddir->dev) > 0)))
	{
		Kfree (lpath);
		lpath=0;
	}

	return 0;
}

/* Minix hard-link, you can't make a hardlink to a directory ... it causes
 * too much trouble, use symbolic links instead.
 */

long m_hardlink(fromdir,fromname,todir,toname)
fcookie *fromdir;
char *fromname;
fcookie *todir;
char *toname;
{
	long finode;
	d_inode rip;
	long pos;

/* Check cross drives */
	if(fromdir->dev!=todir->dev)return EXDEV;

/* Check new doesn't exist and path is otherwise valid */
	finode=search_dir(toname,todir->index,todir->dev,FIND);
	if(finode>0) return EACCDN;
	if(finode!=EFILNF) return finode;

/* Check old path OK */
	if((finode=search_dir(fromname,fromdir->index,fromdir->dev,FIND))<0)
		return finode;

	read_inode(finode,&rip,fromdir->dev);
	if( (!IS_REG(rip) && !IM_SPEC(rip.i_mode))
		|| (rip.i_nlinks >=MINIX_MAX_LINK) ) return EACCDN;

/* Create new entry */
	if((pos=search_dir(toname,todir->index,todir->dev,ADD))<0) return pos;
	{
	  unshort ino = finode;
	  l_write (todir->index, pos, 2L, &ino, todir->dev);
	}
	rip.i_nlinks++;
	rip.i_ctime=Unixtime(Timestamp(),Datestamp());
	write_inode(finode,&rip,fromdir->dev);

	if(cache_mode) l_sync();

	return 0;
}

/* Symbolic links ... basically similar to a regular file with one zone */

long m_symlink(dir,name,to)
fcookie *dir;
char *name;
char *to;
{
	d_inode rip;
	long pos;
	unshort newinode;

	if(!*to)
	{
		DEBUG("m_symlink: invalid null filename");
		return EACCDN;
	}

	/* Strip U: prefix */
	if ((to[0] == 'u' || to[0] == 'U') && to[1] == ':' && to[2] == '\\')
	  to += 2;

	if(strlen(to)>=SYMLINK_NAME_MAX)
	{
		DEBUG("minixfs: Symbolic link name too long");		
		return ERANGE;
	}

	if((pos=search_dir(name,dir->index,dir->dev,ADD))<0) return pos;

	if(!(newinode=alloc_inode(dir->dev)))
	{
		DEBUG("minixfs: symlink drive %c,no free inodes",dir->dev+'A');
		return EACCDN;
	}
	

	bzero(&rip,sizeof(d_inode));
	rip.i_mode=I_SYMLINK | 0777;
	rip.i_size=strlen(to);
	rip.i_uid=Geteuid();
	rip.i_gid=Getegid();
	rip.i_mtime=Unixtime(Timestamp(),Datestamp());
	rip.i_ctime=rip.i_mtime;
	rip.i_atime=rip.i_mtime;
	rip.i_nlinks=1;

	if(!(rip.i_zone[0]=alloc_zone(dir->dev)))
	{
		free_inode(newinode,dir->dev);
		DEBUG("minixfs: symlink drive %c no free zones",dir->dev+'A');
		return EACCDN;
	}
	btos_cpy((char *)&temp,to);
 	write_zone(rip.i_zone[0],&temp,dir->dev,&syscache);
	write_inode(newinode,&rip,dir->dev);
	l_write(dir->index,pos,2L,&newinode,dir->dev);

	if(cache_mode) l_sync();

	return 0;
}

long m_readlink(file,buf,len)
fcookie *file;
char *buf;
int len;
{
	long inum = file->index;
	d_inode rip;

	read_inode(inum,&rip,file->dev);
	if( (rip.i_mode & I_TYPE)!=I_SYMLINK)
	{
		DEBUG("minixfs: attempted readlink on non-symlink");
		return EACCDN;
	}
	read_zone(rip.i_zone[0],&temp,file->dev,&syscache);
	if (temp.bdata[0] == '/' && len > 2)
	  {
	    *buf++ = 'u';
	    *buf++ = ':';
	    len -= 2;
	  }
	if(stob_ncpy(buf, (char *) &temp,len))
	{
		DEBUG("m_readlink: name too long");
		return ERANGE;
	}
	TRACE("m_readlink returned %s",buf);

	return 0;
}

/* the only settable attribute is FA_RDONLY; if the bit is set,
 * the mode is changed so that no write permission exists
 */
long m_chattr(file,attr)
fcookie *file;
int attr;
{
        long inum = file->index;
	int drive = file->dev;
	d_inode rip;

	if ( (attr & FA_RDONLY) ) {
		read_inode(inum,&rip,drive);
		rip.i_mode &= ~(0222);	/* turn off write permission */
		rip.i_ctime=Unixtime(Timestamp(),Datestamp());
		write_inode(inum,&rip,drive);
		if(cache_mode) l_sync();
	} else if (attr == 0) {
		read_inode(inum,&rip,drive);
		if ( (rip.i_mode & 0222) == 0 ) {
			rip.i_mode |= ( (rip.i_mode&0444) >> 1 );
				/* turn write permission back on */
			rip.i_ctime=Unixtime(Timestamp(),Datestamp());
			write_inode(inum,&rip,drive);
			if(cache_mode) l_sync();
		}
	}
	return 0;
}

long m_pathconf(dir,which)
fcookie *dir;
int which;
{
	switch(which) {
	case -1:
		return DP_MAXREQ;
	case DP_IOPEN:
		return UNLIMITED;
	case DP_MAXLINKS:
		 return MINIX_MAX_LINK;
	case DP_PATHMAX:
		return UNLIMITED; /* At last ! */
	case DP_NAMEMAX:
		return MMAX_FNAME(super_ptr[dir->dev]->increment);
	case DP_ATOMIC:
		return BLOCK_SIZE;	/* we can write at least a block atomically */
	case DP_TRUNC:
		return DP_AUTOTRUNC;
	case DP_CASE:
		return DP_CASESENS;	/* Well sort of ... */
	default:
		return EINVFN;
	}
}

long m_release(fc)
fcookie *fc;
{
	return 0;
}

long m_dupcookie(dest,src)
fcookie *dest,*src;
{
	unsigned tmpaux;
	tmpaux=dest->aux;
	*dest=*src;
	if(restore_dev!=-1 && 
	(tmpaux & (AUX_DEV|AUX_DRV))== restore_dev|AUX_DRV )
						dest->dev = tmpaux & AUX_DEV;

	return 0;
}
