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

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

/* a simple unified file system */

# include "unifs.h"
# include "global.h"

# include "libkern/libkern.h"

# include "bios.h"
# include "filesys.h"
# include "kmemory.h"
# include "time.h"

# include "biosfs.h"
# include "kernfs.h"
# include "nullfs.h"
# include "pipefs.h"
# include "procfs.h"
# include "ramfs.h"
# include "shmfs.h"


static long	_cdecl uni_root		(int drv, fcookie *fc);
static long	_cdecl uni_lookup	(fcookie *dir, const char *name, fcookie *fc);
static long	_cdecl uni_getxattr	(fcookie *fc, XATTR *xattr);
static long	_cdecl uni_rmdir	(fcookie *dir, const char *name);
static long	_cdecl uni_remove	(fcookie *dir, const char *name);
static long	_cdecl uni_getname	(fcookie *root, fcookie *dir, char *pathname, int size);
static long	_cdecl uni_rename	(fcookie *olddir, char *oldname, fcookie *newdir, const char *newname);
static long	_cdecl uni_opendir	(DIR *dirh, int flags);
static long	_cdecl uni_readdir	(DIR *dirh, char *nm, int nmlen, fcookie *);
static long	_cdecl uni_pathconf	(fcookie *dir, int which);
static long	_cdecl uni_dfree	(fcookie *dir, long *buf);
static DEVDRV *	_cdecl uni_getdev	(fcookie *fc, long *devsp);
static long	_cdecl uni_symlink	(fcookie *dir, const char *name, const char *to);
static long	_cdecl uni_readlink	(fcookie *fc, char *buf, int buflen);
static long	_cdecl uni_fscntl	(fcookie *dir, const char *name, int cmd, long arg);

FILESYS uni_filesys =
{
	NULL,
	
	/*
	 * 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_REENTRANT_L1	fs is level 1 reentrant
	 * FS_REENTRANT_L2	fs is level 2 reentrant
	 * FS_EXT_1		extensions level 1 - mknod & unmount
	 * FS_EXT_2		extensions level 2 - additional place at the end
	 * FS_EXT_3		extensions level 3 - stat & native UTC timestamps
	 */
	FS_LONGPATH	|
	FS_REENTRANT_L1	|
	FS_REENTRANT_L2	|
	FS_EXT_2	,
	
	uni_root,
	uni_lookup, null_creat, uni_getdev, uni_getxattr,
	null_chattr, null_chown, null_chmode,
	null_mkdir, uni_rmdir, uni_remove, uni_getname, uni_rename,
	uni_opendir, uni_readdir, null_rewinddir, null_closedir,
	uni_pathconf, uni_dfree, null_writelabel, null_readlabel,
	uni_symlink, uni_readlink, null_hardlink, uni_fscntl, null_dskchng,
	NULL, NULL,
	NULL,
	
	/* FS_EXT_1 */
	NULL, NULL,
	
	/* FS_EXT_2
	 */
	
	/* FS_EXT_3 */
	NULL,
	
	0, 0, 0, 0, 0,
	NULL, NULL
};

/*
 * structure that holds files
 * if (mode & S_IFMT == S_IFDIR), then this is an alias for a drive:
 *	"dev" holds the appropriate BIOS device number, and
 *	"data" is meaningless
 * if (mode & S_IFMT == S_IFLNK), then this is a symbolic link:
 *	"dev" holds the user id of the owner, and
 *	"data" points to the actual link data
 */

typedef struct unifile UNIFILE;
struct unifile
{
	char	name[NAME_MAX + 1];
	ushort	mode;
	ushort	dev;
	FILESYS	*fs;
	void	*data;
	UNIFILE	*next;
	ushort	cdate;
	ushort	ctime;
};

static UNIFILE u_drvs[UNI_NUM_DRVS];
static UNIFILE *u_root = 0;

static long	do_ulookup	(fcookie *, const char *, fcookie *, UNIFILE **);

FILESYS *
get_filesys (int dev)
{
	UNIFILE *u;
	
	for (u = u_root; u; u = u->next)
		if (u->dev == dev)
			return u->fs;
	
	return NULL;
}

void
unifs_init (void)
{
	UNIFILE *u = u_drvs;
	int i;
	
	u_root = u;
	for (i = 0; i < UNI_NUM_DRVS; i++, u++)
	{
		u->next = u + 1;
		u->mode = S_IFDIR | DEFAULT_DIRMODE;
		u->dev = i;
		u->cdate = datestamp;
		u->ctime = timestamp;
		
		switch (i)
 		{
			case BIOSDRV:
				strcpy (u->name, "dev");
				u->fs = &bios_filesys;
				break;
			
			case PIPEDRV:
				strcpy (u->name, "pipe");
				u->fs = &pipe_filesys;
				break;
			
			case PROCDRV:
				strcpy (u->name, "proc");
				u->fs = &proc_filesys;
				break;
			
			case RAM_DRV:
				strcpy (u->name, "ram");
				u->fs = &ramfs_filesys;
				break;
			
			case SHM_DRV:
				strcpy (u->name, "shm");
				u->fs = &shm_filesys;
				break;
# if WITH_KERNFS
			case KERNDRV:
				strcpy (u->name, "kern");
				u->fs = &kern_filesys;
				break;
# endif
			case UNIDRV:
				(u-1)->next = u->next;	/* skip this drive */
				break;
			
			default:
				/* drives A..Z1..6 */
				u->name[0] = i + ((i < 26) ? 'a' : '1' - 26);
				u->name[1] = 0;
				u->fs = 0;
				break;
		}
	}
	
	/* oops, we went too far */
	u--;
	u->next = 0;
}

static long _cdecl 
uni_root (int drv, fcookie *fc)
{
	if (drv == UNIDRV)
	{
		fc->fs = &uni_filesys;
		fc->dev = drv;
		fc->index = 0L;
		return E_OK;
	}
	
	fc->fs = 0;
	return EINTERNAL;
}

static long _cdecl 
uni_lookup (fcookie *dir, const char *name, fcookie *fc)
{
	return do_ulookup (dir, name, fc, NULL);
}

/* worker function for uni_lookup; can also return the UNIFILE
 * pointer for the root directory
 */
static long
do_ulookup (fcookie *dir, const char *name, fcookie *fc, UNIFILE **up)
{
	UNIFILE *u;
	long drvs;
	FILESYS *fs;
	fcookie *tmp;
	long changed;

	TRACE (("uni_lookup(%s)", name));

	if (dir->index != 0)
	{
		DEBUG (("uni_lookup: bad directory"));
		return ENOTDIR;
	}
	
	/* special case: an empty name in a directory means that directory
	 * so do "." and ".."
	 */

	if (!*name
		|| (name [0] == '.' && name [1] == '\0')
		|| (name [0] == '.' && name [1] == '.' && name [2] == '\0'))
	{
		dup_cookie (fc, dir);
		return E_OK;
	}
	
	drvs = drvmap() | dosdrvs;
	
	/*
	 * OK, check the list of aliases and special directories
	 */
	for (u = u_root; u; u = u->next)
	{
		if (!strnicmp (name, u->name, NAME_MAX))
		{
			if ((u->mode & S_IFMT) == S_IFDIR)
			{
				if (u->dev >= NUM_DRIVES)
				{
					fs = u->fs;
					if (up) *up = u;
					return xfs_root (fs, u->dev,fc);
				}
				if ((drvs & (1L << u->dev)) == 0)
				{
					return ENOTDIR;
				}
				tmp = &curproc->root[u->dev];
				changed = disk_changed (tmp->dev);
				if (changed || !tmp->fs)
				{
					/* drive changed? */
					if (!changed)
						changedrv (tmp->dev);
					tmp = &curproc->root[u->dev];
					if (!tmp->fs)
					{
						return ENOTDIR;
					}
				}
				dup_cookie (fc, tmp);
			}
			else
			{
				/* a symbolic link */
				fc->fs = &uni_filesys;
				fc->dev = UNIDRV;
				fc->index = (long) u;
			}
			if (up) *up = u;
			return E_OK;
		}
	}
	
	DEBUG (("uni_lookup: name (%s) not found", name));
	return ENOENT;
}

static long _cdecl 
uni_getxattr (fcookie *fc, XATTR *xattr)
{
	UNIFILE *u = (UNIFILE *) fc->index;
	
	if (fc->fs != &uni_filesys)
	{
		ALERT("ERROR: wrong file system getxattr called");
		return EINTERNAL;
	}
	
	xattr->index = fc->index;
	xattr->dev = xattr->rdev = fc->dev;
	xattr->nlink = 1;
	xattr->blksize = 1;
	
	/* If "u" is null, then we have the root directory, otherwise
	 * we use the UNIFILE structure to get the info about it
	 */
	if (!u || ((u->mode & S_IFMT) == S_IFDIR))
	{
		xattr->uid = xattr->gid = 0;
		xattr->size = xattr->nblocks = 0;
		xattr->mode = S_IFDIR | DEFAULT_DIRMODE;
		xattr->attr = FA_DIR;
	}
	else
	{
		xattr->uid = u->dev;
		xattr->gid = 0;
		xattr->size = xattr->nblocks = strlen(u->data) + 1;
		xattr->mode = u->mode;
		xattr->attr = 0;
	}
	
	if (u)
	{
		xattr->mtime = xattr->atime = xattr->ctime = u->ctime;
		xattr->mdate = xattr->adate = xattr->cdate = u->cdate;
	}
	else
	{
		xattr->mtime = xattr->atime = xattr->ctime = timestamp;
		xattr->mdate = xattr->adate = xattr->cdate = datestamp;
	}
	
	return E_OK;
}

static long _cdecl 
uni_rmdir (fcookie *dir, const char *name)
{
	long r;
	
	r = uni_remove (dir, name);
	if (r == ENOENT)
		r = ENOTDIR;
	
	return r;
}

static long _cdecl 
uni_remove (fcookie *dir, const char *name)
{
	UNIFILE *u, *lastu;
	UNUSED (dir);
	
	DEBUG (("uni_remove: %s", name));
	
	lastu = NULL;
	u = u_root;
	while (u)
	{
		if (!strnicmp (u->name, name, NAME_MAX))
		{
			if ((u->mode & S_IFMT) != S_IFLNK)
				return ENOENT;
			
			if (curproc->euid && (u->dev != curproc->euid))
				return EACCES;
			
			kfree (u->data);
			
			if (lastu)
				lastu->next = u->next;
			else
				u_root = u->next;
			
			kfree (u);
			
			return E_OK;
		}
		
		lastu = u;
		u = u->next;
	}
	
	return ENOENT;
}

static long _cdecl 
uni_getname (fcookie *root, fcookie *dir, char *pathname, int size)
{
	FILESYS *fs;
	UNIFILE *u;
	char *n;
	fcookie relto;
	char tmppath[PATH_MAX];
	long r;

	UNUSED(root);

	if (size <= 0)
		return EBADARG;

	fs = dir->fs;
	if (dir->dev == UNIDRV)
	{
		*pathname = 0;
		return E_OK;
	}

	for (u = u_root; u; u = u->next)
	{
		if (dir->dev == u->dev && (u->mode & S_IFMT) == S_IFDIR)
		{
			*pathname++ = '\\';
			if (--size <= 0) return EBADARG;
			for (n = u->name; *n; )
			{
				*pathname++ = *n++;
				if (--size <= 0) return EBADARG;
			}
			break;
		}
	}

	if (!u)
	{
		ALERT("unifs: couldn't match a drive with a directory");
		return ENOTDIR;
	}

	if (dir->dev >= NUM_DRIVES)
	{
		if (xfs_root (fs, dir->dev, &relto) == E_OK)
		{
			if (!(fs->fsflags & FS_LONGPATH))
			{
				r = xfs_getname (fs, &relto, dir, tmppath, PATH_MAX);
				release_cookie (&relto);
				if (r)
				{
					return r;
				}
				if (strlen (tmppath) < size)
				{
					strcpy (pathname, tmppath);
					return E_OK;
				}
				else
				{
					return EBADARG;
				}
			}
			r = xfs_getname (fs, &relto, dir, pathname, size);
			release_cookie(&relto);
			return r;
		}
		else
		{
			*pathname = 0;
			return EINTERNAL;
		}
	}

	if (curproc->root[dir->dev].fs != fs)
	{
		ALERT("unifs: drive's file system doesn't match directory's");
		return EINTERNAL;
	}

	if (!fs)
	{
		*pathname = 0;
		return E_OK;
	}
	if (!(fs->fsflags & FS_LONGPATH))
	{
		r = xfs_getname (fs, &curproc->root[dir->dev], dir, tmppath, PATH_MAX);
		if (r) return r;
		if (strlen (tmppath) < size)
		{
			strcpy (pathname, tmppath);
			return E_OK;
		}
		else
		{
			return EBADARG;
		}
	}
	return xfs_getname (fs, &curproc->root[dir->dev], dir, pathname, size);
}

static long _cdecl 
uni_rename (fcookie *olddir, char *oldname, fcookie *newdir, const char *newname)
{
	UNIFILE *u;
	fcookie fc;
	long r;

	UNUSED (olddir);

	for (u = u_root; u; u = u->next)
	{
		if (!strnicmp (u->name, oldname, NAME_MAX))
			break;
	}

	if (!u)
	{
		DEBUG(("uni_rename: old file not found"));
		return ENOENT;
	}

	/* the new name is not allowed to exist! */
	r = uni_lookup(newdir, newname, &fc);
	if (r == E_OK)
		release_cookie (&fc);

	if (r != ENOENT)
	{
		DEBUG (("uni_rename: error %ld", r));
		return (r == E_OK) ? EACCES : r;
	}

	strncpy (u->name, newname, NAME_MAX);
	
	return E_OK;
}

static long _cdecl 
uni_opendir (DIR *dirh, int flags)
{
	UNUSED (flags);

	if (dirh->fc.index != 0)
	{
		DEBUG (("uni_opendir: bad directory"));
		return ENOTDIR;
	}
	
	dirh->index = 0;
	return E_OK;
}


static long _cdecl 
uni_readdir (DIR *dirh, char *name, int namelen, fcookie *fc)
{
	long map;
	char *dirname;
	int i;
	int giveindex = (dirh->flags == 0);
	UNIFILE *u;
	long index;
	long r;
	
	map = dosdrvs | drvmap();
	
	i = dirh->index++;
	u = u_root;
	while (i > 0)
	{
		i--;
		u = u->next;
		if (!u)
			break;
	}

tryagain:
	if (!u) return ENMFILES;

	dirname = u->name;
	index = (long)u;
	if ((u->mode & S_IFMT) == S_IFDIR)
	{
		/* make sure the drive really exists */
		if (u->dev >= NUM_DRIVES)
		{
			r = xfs_root (u->fs, u->dev,fc);
			if (r)
			{
				fc->fs = &uni_filesys;
				fc->index = 0;
				fc->dev = u->dev;
			}
		}
		else
		{
			if ((map & (1L << u->dev)) == 0)
			{
				dirh->index++;
				u = u->next;
				goto tryagain;
			}
			dup_cookie (fc, &curproc->root[u->dev]);
			if (!fc->fs)
			{
				/* drive not yet initialized
				 * use default attributes
				 */
				fc->fs = &uni_filesys;
				fc->index = 0;
				fc->dev = u->dev;
			}
		}
	}
	else
	{
		/* a symbolic link */
		fc->fs = &uni_filesys;
		fc->dev = UNIDRV;
		fc->index = (long)u;
	}

	if (giveindex)
	{
		namelen -= sizeof (long);
		if (namelen <= 0)
		{
			release_cookie (fc);
			return EBADARG;
		}
		*((long * )name) = index;
		name += sizeof (long);
	}
	if (strlen (dirname) < namelen)
	{
		strcpy (name, dirname);
	}
	else
	{
		release_cookie (fc);
		return EBADARG;
	}
	
	return E_OK;
}

static long _cdecl 
uni_pathconf (fcookie *dir, int which)
{
	UNUSED (dir);
	
	switch (which)
	{
		case DP_INQUIRE:	return DP_XATTRFIELDS;
		case DP_IOPEN:		return 0;		/* no files to open */
		case DP_MAXLINKS:	return 1;		/* no hard links available */
		case DP_PATHMAX:	return PATH_MAX;
		case DP_NAMEMAX:	return NAME_MAX;
		case DP_ATOMIC:		return 1;		/* no atomic writes */
		case DP_TRUNC:		return DP_AUTOTRUNC;
		case DP_CASE:		return DP_CASEINSENS;
		case DP_MODEATTR:	return DP_FT_DIR | DP_FT_LNK;
		case DP_XATTRFIELDS:	return DP_INDEX | DP_DEV | DP_NLINK | DP_SIZE;
	}
	
	return ENOSYS;
}

static long _cdecl 
uni_dfree (fcookie *dir, long *buf)
{
	UNUSED (dir);

	buf[0] = 0;	/* number of free clusters */
	buf[1] = 0;	/* total number of clusters */
	buf[2] = 1;	/* sector size (bytes) */
	buf[3] = 1;	/* cluster size (sectors) */
	
	return E_OK;
}

static DEVDRV * _cdecl 
uni_getdev (fcookie *fc, long *devsp)
{
	UNUSED (fc);

	*devsp = EACCES;
	return E_OK;
}

static long _cdecl 
uni_symlink (fcookie *dir, const char *name, const char *to)
{
	UNIFILE *u;
	fcookie fc;
	long r;
	
	r = uni_lookup (dir, name, &fc);
	if (r == E_OK)
	{
		/* file already exists */
		release_cookie (&fc);
		return EACCES;
	}
	if (r != ENOENT)
	{
		/* some other error */
		return r;
	}
	
	if (curproc->egid)
	{
		/* only members of admin group may do that */
		return EACCES;
	}
	
	u = kmalloc (sizeof (*u));
	if (!u) return ENOMEM;
	
	strncpy (u->name, name, NAME_MAX);
	u->name[NAME_MAX] = 0;
	
	u->data = kmalloc ((long) strlen (to) + 1);
	if (!u->data)
	{
		kfree (u);
		return ENOMEM;
	}
	
	strcpy (u->data, to);
	u->mode = S_IFLNK | DEFAULT_DIRMODE;
	u->dev = curproc->euid;
	u->next = u_root;
	u->fs = &uni_filesys;
	u->cdate = datestamp;
	u->ctime = timestamp;
	u_root = u;
	
	return E_OK;
}

static long _cdecl 
uni_readlink (fcookie *fc, char *buf, int buflen)
{
	UNIFILE *u;
	
	u = (UNIFILE *) fc->index;
	
	assert (u);
	assert ((u->mode & S_IFMT) == S_IFLNK);
	assert (u->data);
	
	if (strlen (u->data) < buflen)
		strcpy (buf, u->data);
	else
		return EBADARG;
	
	return E_OK;
}




/* uk: use these Dcntl's to install a new filesystem which is only visible
 *     on drive u:
 *
 *     FS_INSTALL:   let the kernel know about the file system; it does NOT
 *                   get a device number.
 *     FS_MOUNT:     use Dcntl(FS_MOUNT, "u:\\foo", &descr) to make a directory
 *                   foo where the filesytem resides in; the file system now
 *                   gets its device number which is also written into the
 *                   dev_no field of the fs_descr structure.
 *     FS_UNMOUNT:   remove a file system's directory; this call closes all
 *                   open files, directory searches and directories on this
 *                   device. Make sure that the FS will not recognise any
 *                   accesses to this device, as fs->root will be called
 *                   during the reinitalisation!
 *     FS_UNINSTALL: remove a file system completely from the kernel list,
 *                   but that will only be possible if there is no directory
 *                   associated with this file system.
 *                   This function allows it to write file systems as demons
 *                   which stay in memory only as long as needed.
 *
 * BUG: it is not possible yet to lock such a filesystem.
 */

/* here we start with gemdos only file system device numbers */
static short curr_dev_no = 0x100;

static long _cdecl
uni_fscntl(fcookie *dir, const char *name, int cmd, long arg)
{
	fcookie fc;
	long r;
	
	switch (cmd)
	{
		case MX_KER_XFSNAME:
		{
			strcpy ((char *) arg, "uni-xfs");
			return E_OK;
		}
		case FS_INSTALL: /* install a new filesystem */
		{
			struct fs_descr *d = (struct fs_descr *) arg;
			FILESYS *fs;
			
			if (curproc->euid)
				return EPERM;
			
			/* check if FS is installed already */
			for (fs = active_fs; fs; fs = fs->next)
				if (d->file_system == fs) 
					return 0L;
			
			/* include new file system into chain of file systems */
			xfs_add (d->file_system);
			
			/* return pointer to kernel info as OK */
			return (long) &kernelinfo;
	
		}
		case FS_MOUNT: /* install a new gemdos-only device for this FS */
		{
			struct fs_descr *d = (struct fs_descr *) arg;
			FILESYS *fs;
			UNIFILE *u;
			
			if (curproc->euid)
				return EPERM;
			
			/* first check for existing names */
			r = uni_lookup (dir, name, &fc);
			if (r == E_OK)
			{
				release_cookie (&fc);
				return EACCES; /* name exists already */
			}
			if (r != ENOENT) return r; /* some other error */
			
			if (!d) return EACCES;
			if (!d->file_system) return EACCES;
			
			/* check if FS is installed */
			for (fs = active_fs; fs; fs = fs->next)
				if (d->file_system == fs) 
					break;
			
			if (!fs) return EACCES; /* not installed, so return an error */
			
			u = kmalloc (sizeof (*u));
			if (!u) return ENOMEM;
			
			strncpy (u->name, name, NAME_MAX);
			u->name[NAME_MAX] = 0;
			u->mode = S_IFDIR|DEFAULT_DIRMODE;
			u->data = 0;
			u->fs = d->file_system;
			
			/* now get the file system its own device number */
			u->dev = d->dev_no = curr_dev_no++;
			
			/* chain new entry into unifile list */
			u->next = u_root;
			u_root = u;
			return (long) u->dev;
		}
		case FS_UNMOUNT: /* remove a file system's directory */
		{
			struct fs_descr *d = (struct fs_descr *) arg;
			FILESYS *fs;
			UNIFILE *u;
			
			if (curproc->euid)
				return EPERM;
			
			/* first check that directory exists */
			/* use special uni_lookup mode to get the unifile entry */
			r = do_ulookup(dir, name, &fc, &u);
			if (r != E_OK)  return ENOENT; /* name does not exist */
			
			if (!d) return ENOENT;
			if (!d->file_system) return ENOENT;
			
			if (d->file_system != fc.fs)
				return ENOENT; /* not the right name! */
			
			release_cookie (&fc);
			
			if (!u || (u->fs != d->file_system))
				return ENOENT;
			
			/* check if FS is installed */
			for (fs = active_fs;  fs;  fs = fs->next)
				if (d->file_system == fs) 
					break;
			
			if (!fs) return EACCES; /* not installed, so return an error */

			/* here comes the difficult part: we have to close all files on that
			 * device, so we have to call changedrv(). The file system driver
			 * has to make sure that further calls to fs.root() with this device
			 * number will fail!
			 *
			 * Kludge: mark the directory as a link, so uni_remove will remove it.
			 */
			changedrv(u->dev);
			u->mode &= ~S_IFMT;
			u->mode |= S_IFLNK;
			return uni_remove (dir, name);
		}
		case FS_UNINSTALL: /* remove file system from kernel list */
		{
			struct fs_descr *d = (struct fs_descr *) arg;
			FILESYS *fs, *last_fs;
			UNIFILE *u;
			
			if (curproc->euid)
				return EPERM;
			
			/* first check if there are any files or directories associated with
			 * this file system
			 */
			for (u = u_root;  u;  u = u->next)
				if (u->fs == d->file_system)
					/* we cannot remove it before unmount */
					return EACCES;
			last_fs = 0;
			fs = active_fs;
			
			/* go through the list and remove the file system */
			while (fs)
			{
				if (fs == d->file_system)
				{
					if (last_fs)
						last_fs->next = fs->next;
					else
						active_fs = fs->next;
					d->file_system->next = 0;
					return E_OK;
				}
				last_fs = fs;
				fs = fs->next;
			}
			return ENOENT;
		}
		case FS_INFO:
		{
			struct fs_info *info = (struct fs_info *) arg;
			if (info)
			{
				char *dst = info->type_asc;

				strcpy(info->name, "uni-xfs");
				info->version = (long) UNIFS_MAJOR << 16;
				info->version |= (long) UNIFS_MINOR;
				info->type = 0;			/* FIXME */
				strcpy(dst, "root");
			}
			return E_OK;
		}
		default:
		{
			/* see if we should just pass this along to another file system */
			r = uni_lookup (dir, name, &fc);
			if (r == E_OK)
			{
				if (fc.fs != &uni_filesys)
				{
					r = xfs_fscntl (fc.fs, &fc, ".", cmd, arg);
					release_cookie (&fc);
					return r;
				}
				else if (cmd == FUTIME)
				{
					UNIFILE *u = (UNIFILE *) fc.index;
					
					if (u)
					{
						u->ctime = timestamp;
						u->cdate = datestamp;
					}
					
					release_cookie (&fc);
					return E_OK;
				}
				
				release_cookie (&fc);
			}
		}
	}
	
	DEBUG (("uni_fscntl(%s, cmd %x, arg %lx) fail!", name, cmd, arg));
	return ENOSYS;
}
