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

# include "minixfs.h"
# include "minixdev.h"
# include "global.h"
# include "version.h"

# include "bitmap.h"
# include "dir.h"
# include "inode.h"
# include "main.h"
# include "misc.h"
# include "trans.h"
# include "zone.h"


static void	set_atime	(fcookie *fc);
static void	m_invalidate	(int drv);


static long	m_root		(int dev, fcookie *dir);

static long	m_lookup	(fcookie *dir, const char *name, fcookie *entry);
static DEVDRV *	m_getdev	(fcookie *file, long *special);
static long	m_getxattr	(fcookie *file, XATTR *xattr);

static long	m_chattr	(fcookie *file, int attr);
static long	m_chown		(fcookie *file, int uid, int gid);
static long	m_chmode	(fcookie *file, unsigned mode);

static long	m_mkdir		(fcookie *dir, const char *name, unsigned mode);
static long	m_rmdir		(fcookie *dir, const char *name);
static long	m_creat		(fcookie *dir, const char *name, unsigned mode, int attr, fcookie *entry);
static long	m_remove	(fcookie *dir, const char *name);
static long	m_getname	(fcookie *root, fcookie *dir, char *pathname, int length);
static long	m_rename	(fcookie *olddir, char *oldname, fcookie *newdir, const char *newname);

static long	m_opendir	(DIR *dirh, int flag);
static long	m_readdir	(DIR *dirh, char *name, int namelen, fcookie *fc);
static long	m_rewinddir	(DIR *dirh);
static long	m_closedir	(DIR *dirh);

static long	m_pathconf	(fcookie *dir, int which);
static long	m_dfree		(fcookie *dir, long *buffer);
static long	m_wlabel	(fcookie *dir, const char *name);
static long	m_rlabel	(fcookie *dir, char *name, int namelen);

static long	m_symlink	(fcookie *dir, const char *name, const char *to);
static long	m_readlink	(fcookie *file, char *buf, int len);
static long	m_hardlink	(fcookie *fromdir, const char *fromname, fcookie *todir, const char *toname);
static long	m_fscntl	(fcookie *dir, const char *name, int cmd , long arg);
static long	m_dskchng	(int d, int mode);

static long	m_release	(fcookie *fc);
static long	m_dupcookie	(fcookie *dest, fcookie *src);
static long	m_sync		(void);


FILESYS minix_filesys =
{
	(FILESYS *) 0,
	/*
	 * FS_KNOPARSE		kernel shouldn't do parsing
	 * FS_CASESENSITIVE	file names are case sensitive
	 * FS_NOXBIT		if a file can be read, it can be executed
	 * FS_LONGPATH		file system understands "size" argument to "getname"
	 * FS_NO_C_CACHE	don't cache cookies for this filesystem
	 * FS_DO_SYNC		file system has a sync function
	 * FS_OWN_MEDIACHANGE	filesystem control self media change (dskchng)
	 */
	FS_CASESENSITIVE | FS_LONGPATH | FS_DO_SYNC | FS_OWN_MEDIACHANGE,
	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,
	m_sync
};

static short restore_dev = -1;

/* 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!)
 */

static void
set_atime (fcookie *fc)
{
	d_inode rip;
	
	if (fc->dev > 1)
	{
		read_inode (fc->index, &rip, fc->dev);
		rip.i_atime = Unixtime (Timestamp (), Datestamp ());
		write_inode (fc->index, &rip, fc->dev);
	}
}

/*
 * Invalidate all cache entries for a given drive
 */

static void
m_invalidate (int drv)
{
	super_info *psblk = super_ptr[drv];
	
	bio.invalidate (psblk->di);
	
	if (psblk->idirty)
	{
		ALERT ("Minix-FS: Inode bitmap not written out when drive %c invalidated", drv+'A');
	}
	if (psblk->zdirty)
	{
		ALERT ("Minix-FS: Zone bitmap not written out when drive %c invalidated", drv+'A');
	}
	
	psblk->idirty =	0;
	psblk->zdirty = 0;
}

void
sync_bitmaps (register super_info *psblk)
{
	register void *buf = NULL;
	register long start = 0;
	register long size = 0;
	
	if (psblk->idirty)
	{
		buf = psblk->ibitmap;
		start = 2;
		size = psblk->sblk.s_imap_blks;
		
		if (psblk->zdirty)
			size += psblk->sblk.s_zmap_blks;
	}
	else if (psblk->zdirty)
	{
		buf = psblk->zbitmap;
		start = psblk->sblk.s_imap_blks + 2;
		size = psblk->sblk.s_zmap_blks;
	}
	
	if (start)
	{
		size *= BLOCK_SIZE;
		BIO_RWABS (psblk->di, 3, buf, size, start);
	}
	
	psblk->idirty = 0;
	psblk->zdirty = 0;
}

/*
 * 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.
 */

static long
m_root (int dev, fcookie *dir)
{
	super_info **psblk = super_ptr + dev;
	
	DEBUG ("Minix-FS (%c): m_root enter", dev+'A');
	
	/* If not present, see if it's valid */
	if (!*psblk)
	{
		long i = minix_sanity (dev);
		if (i)
		{
			DEBUG ("Minix-FS (%c): m_root leave %li", dev+'A', i);
			return i;
		}
		
		/* default: enable writeback mode */
		(void) bio.config (dev, BIO_WB, ENABLE);
	}
	
	if (*psblk)
	{
		dir->fs = &minix_filesys;
		
		/* Aux field tells original device */
		dir->aux = dev | AUX_DRV ;
		dir->index = ROOT_INODE;
		
		dir->dev = dev;
		
		DEBUG ("Minix-FS (%c): m_root leave E_OK", dev+'A');
		return E_OK;
	}
	
	DEBUG ("Minix-FS (%c): m_root leave EDRIVE", dev+'A');
	return EDRIVE;
}

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

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

	if (dir->index == ROOT_INODE && !_STRCMP (name, ".."))
	{
		*entry = *dir;
		
		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;
	
	return 0;
}

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

static long
m_getxattr (fcookie *file, XATTR *xattr)
{
	super_info *psblk = super_ptr[file->dev];
	d_inode rip;
	long time_tmp;
	long extra;
	
	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;
}

/* the only settable attribute is FA_RDONLY; if the bit is set,
 * the mode is changed so that no write permission exists
 */
static long
m_chattr (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);
		sync (drive);
	}
	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);
			sync (drive);
		}
	}
	return 0;
}

static long
m_chown (fcookie *file, int uid, int 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);
	
	sync (file->dev);
	return 0;
}

static long
m_chmode (fcookie *file, unsigned int mode)
{
	d_inode rip;
	
	read_inode (file->index, &rip, file->dev);
	
	rip.i_mode = (rip.i_mode & I_TYPE) | (mode & ALL_MODES);                
	rip.i_ctime = Unixtime (Timestamp (), Datestamp ());
	write_inode (file->index, &rip, file->dev);

	sync (file->dev);
	return 0;
}

static long
m_mkdir (fcookie *dir, const char *name, unsigned int mode)
{
	ushort newdir;
	d_inode rip, ripnew;
	long pos;
	int incr = super_ptr[dir->dev]->incr;
	dir_struct blank[MAX_INCREMENT*2];
	
	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 (dir->dev, newdir);
		
		sync (dir->dev);
		return EACCDN;
	}
	
	rip.i_nlinks++;
	write_inode (dir->index, &rip, dir->dev);
	l_write (dir->index, pos, 2L, &newdir, dir->dev);
	
	sync (dir->dev);
	return E_OK;
}

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

	read_inode (inum, &rip, dir->dev);
	read_inode (dir->index, &rip2, dir->dev);
	if (!IS_DIR (rip)) return EFILNF;
	incr = super_ptr[dir->dev]->incr;
	
	/* Check if dir is actually empty */
	for (chunk = 0; (left = next_zone (&rip, chunk) / DIR_ENTRY_SIZE); chunk++)
	{
		long ztemp = find_zone (&rip, chunk, dir->dev, 0);
		read_zone (ztemp, &temp, dir->dev);
		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 (dir->dev, inum);
	}
	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);

	sync (dir->dev);
	return 0;
}

static long
m_creat (fcookie *dir, const char *name, unsigned int mode, int attr, fcookie *entry)
{
	long pos;
	d_inode ripnew;
	ushort 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 ("Minix-FS (%c): m_creat: no free inodes.", dir->dev+'A');
		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);

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

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

static long
m_remove (fcookie *dir, const 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 (dir->dev, inum);
		}
	}
	write_inode (inum, &rip, dir->dev);
	
	sync (dir->dev);
	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 */

static long
m_getname (fcookie *root, fcookie *dir, char *pathname, int length)
{
	super_info *psblk = super_ptr[dir->dev];
	long inum, pinum;
	ushort dev;
	int chunk;
	long left;
	int incr;
	short plength;
	
	*pathname = 0;
	
	if (dir->dev == root->dev && dir->index == root->index)
		return 0;
	
	incr = psblk->incr;
	inum = dir->index;
	dev = dir->dev;
	plength = 0;
	
	while (inum != root->index && inum != ROOT_INODE)
	{
		d_inode rip;
		pinum = search_dir ("..", inum, dev, FIND); 
		/* Parent inum */
		
		if (pinum < 0) /* If this happens we're in trouble */
		{
			ALERT ("Minix-FS (%c): m_getname: no '..' in inode %d", dev+'A', inum);
			return pinum;
		}
		read_inode (pinum, &rip, dev);
		for (chunk = 0; (left = next_zone (&rip, chunk) / DIR_ENTRY_SIZE) && inum != pinum; chunk++)
		{
			UNIT *u = cget_zone (find_zone (&rip, chunk, dev, 0), dev);
			
			char tname[MNAME_MAX+1];
			int i;
			for (i = 0; i < left && inum != pinum; i += incr)
				if (((bufr *) u->data)->bdir[i].d_inum == inum)
				{
					_STRNCPY (tname, ((bufr *) u->data)->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 ("Minix-FS (%c): m_getname: inode %d orphaned or bad '..'", dev+'A', inum);
			return EINTRN;
		}
	}
	if (inum == ROOT_INODE && root->index != ROOT_INODE)
	{
		DEBUG ("Minix-FS (%c): m_getname: Hmmmm root is not a parent of dir.", dev+'A');
		return EINTRN;
	}
	strrev (pathname);
	return 0;
}

/* 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.
 */

static long
m_rename (fcookie *olddir, char *oldname, fcookie *newdir, const 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 ("Minix-FS (%c): valid directory move", olddir->dev+'A');
			dirmove = 1;
# endif
		}
	}

	/* 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;
	{
		ushort 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 ("Minix-FS (%c): m_rename: no '..' in inode %ld.", olddir->dev+'A', finode);
			return EACCDN;
		}
		if (pos != DIR_ENTRY_SIZE * super_ptr[newdir->dev]->incr)
			ALERT ("Minix-FS (%c): m_rename: Unexpected '..' position in inode %ld.", olddir->dev+'A', finode);
		{
			ushort 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);
	}
	
	sync (olddir->dev);
	return 0;
}

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

static long
m_readdir (DIR *dirh, char *name, int namelen, fcookie *fc)
{
	super_info *psblk = super_ptr[dirh->fc.dev];
        d_inode rip;
	unsigned entry, chunk;
	long limit;
	int flag, incr;
	
	if (dirh->flags & TOS_SEARCH)
		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->incr;

	while ((limit = next_zone (&rip, chunk) / DIR_ENTRY_SIZE))
	{
		UNIT *u = cget_zone (find_zone (&rip, chunk, dirh->fc.dev, 0), dirh->fc.dev);
		
		while (entry < limit)
	  	{
			dir_struct *try = &((bufr *) u->data)->bdir[entry];
			entry += incr;
			if (try->d_inum)
			{
				char tmpbuf[MNAME_MAX + 8];
				char *tmpnam;
				
				tmpnam = tosify (try->d_name, flag, tmpbuf, MMAX_FNAME (incr));
				
				if ((dirh->flags & TOS_SEARCH) == 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 (WB_CHECK (psblk))
					set_atime (&dirh->fc);
				
				return 0;
			}
		}
		if (entry != NR_DIR_ENTRIES) return ENMFIL;
		else entry = 0;
		chunk++;
	}
	return ENMFIL;
}

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

static long
m_closedir (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 (!WB_CHECK (super_ptr[dirh->fc.dev]))
	{
		set_atime (&dirh->fc);
		sync (dirh->fc.dev);
	}
	
	dirh->fc.index = 0;
	return 0;
}

static long
m_pathconf (fcookie *dir, int which)
{
	switch (which)
	{
		case DP_INQUIRE:	return DP_VOLNAMEMAX;
		case DP_IOPEN:		return UNLIMITED;
		case DP_MAXLINKS:	return MINIX_MAX_LINK;
		case DP_PATHMAX:	return UNLIMITED;
		case DP_NAMEMAX:	return MMAX_FNAME (super_ptr[dir->dev]->incr);
		case DP_ATOMIC:		return BLOCK_SIZE;
		case DP_TRUNC:		return DP_AUTOTRUNC;
		case DP_CASE:		return DP_CASESENS;
		case DP_MODEATTR:	return (DP_ATTRBITS
						| DP_MODEBITS
						| DP_FT_DIR
						| DP_FT_REG
						| DP_FT_LNK
					);
		case DP_XATTRFIELDS:	return (DP_INDEX
						| DP_DEV
						| DP_NLINK
						| DP_UID
						| DP_GID
						| DP_BLKSIZE
						| DP_SIZE
						| DP_NBLOCKS
						| DP_ATIME
						| DP_CTIME
						| DP_MTIME
					);
		case DP_VOLNAMEMAX:	return 0;
	}
	
	return EINVFN;
}

static long
m_dfree (fcookie *dir, long int *buffer)
{
	super_info *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;
}

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

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

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

long
m_symlink (fcookie *dir, const char *name, const char *to)
{
	bufr temp;
	d_inode rip;
	long pos;
	ushort newinode;

	if (!*to)
	{
		DEBUG ("Minix-FS (%c): m_symlink: invalid null filename.", dir->dev+'A');
		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 ("Minix-FS (%c): m_symlink: symbolic link name too long.", dir->dev+'A');		
		return ERANGE;
	}

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

	if (!(newinode = alloc_inode (dir->dev)))
	{
		DEBUG ("Minix-FS (%c): m_symlink: 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 (dir->dev, newinode);
		DEBUG ("Minix-FS (%c): m_symlink: no free zones.", dir->dev+'A');
		return EACCDN;
	}
	btos_cpy ((char *) &temp, to);
 	write_zone (rip.i_zone[0], &temp, dir->dev);
	write_inode (newinode, &rip, dir->dev);
	l_write (dir->index, pos, 2L, &newinode, dir->dev);
	
	sync (dir->dev);
	return 0;
}

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

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

static long
m_hardlink (fcookie *fromdir, const char *fromname, fcookie *todir, const 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;
	
	{
		ushort 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);
	
	sync (fromdir->dev);
	return 0;
}

static long
m_fscntl (fcookie *dir, const char *name, int cmd, long int arg)
{
	FILEPTR *f;
	long inum;
	int uid, gid, id;
	d_inode rip;
	extern long init_addr;
	
	uid = Geteuid();
	gid = Getegid();

	switch (cmd)
	{
		case MX_KER_XFSNAME:
		{
			strcpy ((char *) arg, "MinixFS");
			return E_OK;
		}
		case FS_INFO:
		{
			struct fs_info *info = (struct fs_info *) arg;
			if (info)
			{
				strcpy (info->name, "minix-xfs");
				info->version = ((long) MFS_MAJOR << 16) | MFS_MINOR;
				info->type = FS_MINIX;
				strcpy (info->type_asc, "minix v2");
			}
			return E_OK;
		}
		case FS_USAGE:
		{
			super_info *psblk = super_ptr[dir->dev];
			struct fs_usage *inf = (struct fs_usage *) arg;
			
			inf->blocksize = BLOCK_SIZE;
			inf->blocks.hi = 0;
			inf->blocks.lo = psblk->sblk.s_zones - psblk->sblk.s_firstdatazn;
			inf->free_blocks.hi = 0;
			inf->free_blocks.lo = inf->blocks.lo - count_bits (psblk->zbitmap, inf->blocks.lo + 1) + 1;
			inf->inodes.hi = 0;
			inf->inodes.lo = psblk->sblk.s_ninodes;
			inf->free_inodes.hi = 0;
			inf->free_inodes.lo = inf->inodes.lo - count_bits (psblk->ibitmap, inf->inodes.lo + 1) + 1;
			
			return E_OK;
		}
		case MFS_VERIFY:
		{
			*((long *)arg) = MFS_MAGIC;
			return E_OK;
		}
		case MFS_SYNC:
		/* Sync the filesystem */
		{
			sync_bitmaps (super_ptr[dir->dev]);
			bio.sync_drv (super_ptr[dir->dev]->di);
			
			TRACE ("Minix-FS (%c): Done sync", dir->dev+'A');
			return E_OK;
		}
		case MFS_CINVALID:
		/* Invalidate all cache entries for a given drive */
		{
			if (uid) return EACCDN;
			m_invalidate (dir->dev);
			return E_OK;
		}
		case MFS_FINVALID:
		/* Invalidate all fileptrs for a given drive */
		{
			if (uid) return EACCDN;
			id = Getpid ();
			for (f = firstptr; f; f = f->next)
				if (f->fc.dev == dir->dev)
					m_close (f, id);
			return E_OK;
		}
		case MFS_INFO:
		{
			super_info *psblk = super_ptr[dir->dev];
			mfs_info *inf = (mfs_info *) arg;
			
			inf->total_zones = psblk->sblk.s_zones-psblk->sblk.s_firstdatazn;
			inf->total_inodes = psblk->sblk.s_ninodes;
			inf->version = 2;
			inf->increment = psblk->incr;		
			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 E_OK;
		}
		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 E_OK;
		}
		case MFS_GTRANS:
		{
			*((long *) arg) = fs_mode[dir->dev];
			return E_OK;
		}
		case MFS_STRANS:
		{
			if (uid) return EACCDN;
			fs_mode[dir->dev] = *((long *) arg);
			return E_OK;
		}
		case MFS_IADDR:
		{
			*((long *) arg) = (long) &init_addr;
			return E_OK;
		}
		case MFS_UPDATE:
		{
			/* no longer supported */
			break;
		}
		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);
				
				sync (fc.dev);
				return E_OK;
			}

			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));
			
	  		sync (fc.dev);
			return E_OK;
		}
		case MFS_LOPEN:
		{
			openf_list *flist = (openf_list *) arg;
			long 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 E_OK;
		}
		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);
			
			sync (dir->dev);
			return E_OK;
		}
		case V_CNTR_WB:
		{
			return bio.config (dir->dev, BIO_WB, arg);
		}
	}
	
	return EINVFN;
}

/*
 * the kernel calls this when it detects a disk change.
 */

static long
m_dskchng (int d, int mode)
{
	super_info *psblk = super_ptr[d];
	long change = 1;
	FILEPTR *f, **last;
	
	TRACE ("Minix-FS (%c): m_dskchng: enter (mode = %i)", d+'A', mode);
	
	if (mode == 0)
		change = BIO_DSKCHNG (psblk->di);
	
	if (change == 0)
	{
		/* no change */
		TRACE ("Minix-FS (%c): m_dskchng: leave no change!", d+'A');
		return change;
	}
	
	/* Since the disk has changed always invalidate cache */
	m_invalidate (d);
	
	bio.free_di (psblk->di);
	
	Kfree (psblk->ibitmap);
	Kfree (psblk);
	super_ptr[d] = 0;
	
	/* 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;
	}
	return 1;
}

static long
m_release (fcookie *fc)
{
	return E_OK;
}

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

static long
m_sync (void)
{
	int i;
	
	for (i = 0; i < NUM_DRIVES; i++)
	{
		register super_info *psblk = super_ptr[i];
		if (psblk)
		{
			sync_bitmaps (psblk);
		}
	}
	
	/* buffer cache automatically synced */
	return E_OK;
}
