[BACK]Return to xfs_iops.c CVS log [TXT][DIR] Up to [Development] / xfs-linux / linux-2.6

File: [Development] / xfs-linux / linux-2.6 / xfs_iops.c (download)

Revision 1.21, Sun Jan 30 09:59:06 2000 UTC (17 years, 8 months ago) by kenmcd
Branch: MAIN
Changes since 1.20: +11 -16 lines

Encumbrance review done.
Add copyright and license words consistent with GPL.
Refer to http://fsg.melbourne.sgi.com/reviews/ for details.

There is a slight change in the license terms and conditions words
to go with the copyrights, so most of the files are not getting
new GPL's, just updated versions ... but there are 20-30 more files
here as well.

/*
 * Copyright (C) 1999 Silicon Graphics, Inc.  All Rights Reserved.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
 */

/*
 *  fs/xfs/linux/xfs_iops.c
 *     This file would be called xfs_inode.c, but that is already taken
 *     in the standard XFS code.
 *
 */

#define FSID_T
#include <sys/types.h>
#include <linux/errno.h>
#include "xfs_coda_oops.h"

#include <linux/xfs_to_linux.h>

#undef  NODEV
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/sched.h>	/* To get current */
#include <linux/locks.h>
#include <linux/slab.h>

#include <linux/linux_to_xfs.h>

#include <sys/sysmacros.h>
#include <sys/capability.h>
#include <sys/cred.h>
#include <sys/buf.h>
#include <sys/vfs.h>
#include <sys/pvfs.h>
#include <sys/vnode.h>
#include <ksys/behavior.h>
#include <sys/mode.h>
#include <xfs_linux.h>

#include <linux/xfs_file.h>

#include <linux/page_buf.h>

#include <asm/uaccess.h> /* For copy_from_user */

/*
 * Common code used to create/instantiate various things in a directory.
 */

int linvfs_common_cr(struct inode *dir, struct dentry *dentry, int mode,
				enum vtype tp, int rdev)
{
	int		error = 0;
	vnode_t		*dvp, *vp;
	struct inode	*ip;
	vattr_t		va;
	ino_t		ino;

	dvp = LINVFS_GET_VP(dir);
	ASSERT(dvp);

	vp = NULL;

	bzero(&va, sizeof(va));
	va.va_mask = AT_TYPE|AT_MODE;
	va.va_type = tp;
	va.va_mode = mode;	/* Do we need to pass this through? JIMJIM
				va.va_mode = 0777 & ~current->fs->umask; */
	va.va_size = 0;

	if (tp == VREG) {
		VOP_CREATE(dvp, (char *)dentry->d_name.name, &va, 0, 0, &vp,
				sys_cred, error);
	} else if ((tp == VBLK) || (tp == VCHR)) {
		/*
		 * Get the real type from the mode
		 */
		va.va_rdev = makedev(MAJOR(rdev), MINOR(rdev));
		va.va_mask |= AT_RDEV;

		va.va_type = IFTOVT(mode);
		if (va.va_type == VNON) {
			return -EINVAL;
		}
		VOP_CREATE(dvp, (char *)dentry->d_name.name, &va, 0, 0, &vp,
			sys_cred, error);
	} else if (tp == VDIR) {
		VOP_MKDIR(dvp, (char *)dentry->d_name.name, &va, &vp,
			sys_cred, error);
	}

	if (!error) {
		ASSERT(vp);
		bzero(&va, sizeof(va));
		va.va_mask = AT_NODEID;
		VOP_GETATTR(vp, &va, 0, sys_cred, error);
		if (error) {
			VN_RELE(vp);
			return -error;
		}
		ino = va.va_nodeid;
		ASSERT(ino);
		ip = iget(dir->i_sb, ino);
		if (!ip) {
			VN_RELE(vp);
			return -ENOMEM;
		}
		d_instantiate(dentry, ip);
	}
	return 0;
}


/*
 * Create a new file in dir using mode.
 */
int linvfs_create(struct inode *dir, struct dentry *dentry, int mode)
{
	return(linvfs_common_cr(dir, dentry, mode, VREG, 0));
}

struct dentry * linvfs_lookup(struct inode *dir, struct dentry *dentry)
{
	int		error;
	vnode_t		*vp, *cvp;
	pathname_t	pn;
	pathname_t      *pnp = &pn;
	struct inode	*ip = NULL;
	vattr_t		va;
	ino_t		ino;

	vp = LINVFS_GET_VP(dir);
	ASSERT(vp);

	/*
	 * Initialize a pathname_t to pass down.
	 */
	bzero(pnp, sizeof(pathname_t));
	pnp->pn_complen = dentry->d_name.len;
	pnp->pn_hash = dentry->d_name.hash;
	pnp->pn_path = (char *)dentry->d_name.name;

	cvp = NULL;

	VOP_LOOKUP(vp, (char *)dentry->d_name.name, &cvp, pnp, 0, NULL,
			sys_cred, error);
	if (!error) {
		ASSERT(cvp);
		bzero(&va, sizeof(va));
		va.va_mask = AT_NODEID;
		VOP_GETATTR(cvp, &va, 0, sys_cred, error);
		if (error) {
			VN_RELE(cvp);
			return ERR_PTR(-error);
		}
		ino = va.va_nodeid;
		ASSERT(ino);
		ip = iget(dir->i_sb, ino);
		if (!ip) {
			VN_RELE(cvp);
			return ERR_PTR(-EACCES);
		}
	}
	d_add(dentry, ip);	/* Negative entry goes in if ip is NULL */
	return NULL;
}


int linvfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
{
	int		error;
	vnode_t		*tdvp;	/* Target directory for new name/link */
	vnode_t		*vp;	/* vp of name being linked */
	struct inode	*ip;	/* inode of guy being linked to */

	tdvp = LINVFS_GET_VP(dir);
	ASSERT(tdvp);

	ip = old_dentry->d_inode;	/* inode being linked to */
	vp = LINVFS_GET_VP(ip);

	error = 0;
	VOP_LINK(tdvp, vp, (char *)dentry->d_name.name, sys_cred, error);
	if (!error) {
		ip->i_nlink++;
		ip->i_ctime = CURRENT_TIME;
		mark_inode_dirty(ip);
		ip->i_count++;
		VN_HOLD(vp);
		d_instantiate(dentry, ip);
	}
	return -error;
}


int linvfs_unlink(struct inode *dir, struct dentry *dentry)
{
	int		error;
	struct inode	*inode;
	vnode_t		*dvp;	/* directory containing name to remove */

	dvp = LINVFS_GET_VP(dir);
	inode = dentry->d_inode;
	ASSERT(dvp);

	/*
	 * Someday we could pass the dentry->d_inode into VOP_REMOVE so
	 * that it can skip the lookup.
	 */

	error = 0;
	VOP_REMOVE(dvp, (char *)dentry->d_name.name, sys_cred, error);
	if (!error) {
		dir->i_ctime = dir->i_mtime = CURRENT_TIME;
		dir->i_version = ++event;
		inode->i_nlink--;
		inode->i_ctime = dir->i_ctime;
		d_delete(dentry);       /* del and free inode */
	}
	return -error;
}


int linvfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
{
	int		error;
	vnode_t		*dvp;	/* directory containing name to remove */
	vnode_t		*cvp;	/* used to lookup symlink to put in dentry */
	vattr_t		va;
	pathname_t	pn;
	pathname_t      *pnp = &pn;
	struct inode	*ip = NULL;
	ino_t		ino;

	dvp = LINVFS_GET_VP(dir);
	ASSERT(dvp);

	bzero(&va, sizeof(va));
	va.va_type = VLNK;
	va.va_mode = 0777 & ~current->fs->umask;
	va.va_mask = AT_TYPE|AT_MODE; /* AT_PROJID? */
	error = 0;
	VOP_SYMLINK(dvp, (char *)dentry->d_name.name, &va, (char *)symname,
		sys_cred, error);
	if (!error) {
		/* Now need to lookup the name since we didn't get the vp back */
		/* Maybe modify VOP_SYMLINK to return *vpp? */
		/* But, lookup should hit DNLC, anyway */

		/*
		 * Initialize a pathname_t to pass down.
		 */
		bzero(pnp, sizeof(pathname_t));
		pnp->pn_complen = dentry->d_name.len;
		pnp->pn_hash = dentry->d_name.hash;
		pnp->pn_path = (char *)dentry->d_name.name;

		cvp = NULL;
		VOP_LOOKUP(dvp, (char *)dentry->d_name.name, &cvp, pnp, 0, NULL,
					sys_cred, error);
		if (!error) {
			ASSERT(cvp);
			bzero(&va, sizeof(va));
			va.va_mask = AT_NODEID;
			VOP_GETATTR(cvp, &va, 0, sys_cred, error);
			if (error) {
				VN_RELE(cvp);
				return error;
			}
			ino = va.va_nodeid;
			ASSERT(ino && (cvp->v_type == VLNK));
			ip = iget(dir->i_sb, ino);
			if (!ip) {
				error = -ENOMEM;
				VN_RELE(cvp);
			} else {
				d_instantiate(dentry, ip);
			}
		}
	}
	return -error;
}


int linvfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
{
	return(linvfs_common_cr(dir, dentry, mode, VDIR, 0));
}


int linvfs_rmdir(struct inode *dir, struct dentry *dentry)
{
	int		error;
	vnode_t		*dvp,	/* directory containing name to remove */
			*pwd_vp; /* current working directory, vnode */

	dvp = LINVFS_GET_VP(dir);
	ASSERT(dvp);
	ASSERT(current->fs->pwd->d_inode);

	pwd_vp = LINVFS_GET_VP(current->fs->pwd->d_inode);

	/*
	 * Someday we could pass the dentry->d_inode into VOP_REMOVE so
	 * that it can skip the lookup.
	 */

	error = 0;
	VOP_RMDIR(dvp, (char *)dentry->d_name.name, pwd_vp, sys_cred, error);
	if (!error) {
		ASSERT(dir->i_nlink == 2);
		dir->i_nlink = 0;
		dir->i_size = 0;
		dir->i_version = ++event;
		mark_inode_dirty(dir);
		dir->i_nlink--;
		dir->i_version = ++event;
		dir->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME;
		mark_inode_dirty(dir);
		d_delete(dentry);       /* del and free inode */
	}
	return -error;
}


int linvfs_mknod(struct inode *dir, struct dentry *dentry, int mode, int rdev)
{
	return(linvfs_common_cr(dir, dentry, mode, VBLK, rdev));
}


int linvfs_rename(struct inode *odir, struct dentry *odentry,
	       struct inode *ndir, struct dentry *ndentry)
{
	int		error;
	vnode_t		*fvp;	/* from directory */
	vnode_t		*tvp;	/* target directory */
	pathname_t	pn;
	pathname_t      *pnp = &pn;
	struct inode	*new_inode = NULL;

	bzero(pnp, sizeof(pathname_t));
	pnp->pn_complen = ndentry->d_name.len;
	pnp->pn_hash = ndentry->d_name.hash;
	pnp->pn_path = (char *)ndentry->d_name.name;

	fvp = LINVFS_GET_VP(odir);
	tvp = LINVFS_GET_VP(ndir);

	new_inode = ndentry->d_inode;

	VOP_RENAME(fvp, (char *)odentry->d_name.name, tvp,
		   (char *)ndentry->d_name.name, pnp, sys_cred, error);
	if (error)
		return -error;

	ndir->i_version = ++event;
	odir->i_version = ++event;
	if (new_inode) {
		new_inode->i_nlink--;
		new_inode->i_ctime = CURRENT_TIME;
		mark_inode_dirty(new_inode);
	}

	odir->i_ctime = odir->i_mtime = CURRENT_TIME;
	mark_inode_dirty(odir);
	return 0;
}


int linvfs_readlink(struct dentry *dentry, char *buf, int size)
{
	vnode_t	*vp;
	uio_t	uio;
	iovec_t	iov;
	int	error = 0;

	vp = LINVFS_GET_VP(dentry->d_inode);
	iov.iov_base = buf;
	iov.iov_len = size;

	uio.uio_iov = &iov;
	uio.uio_offset = 0;
	uio.uio_segflg = UIO_USERSPACE;
	uio.uio_resid = size;

	UPDATE_ATIME(dentry->d_inode);
	VOP_READLINK(vp, &uio, sys_cred, error);
	if (error)
		return -error;

	return (size - uio.uio_resid);
}


struct dentry * linvfs_follow_link(struct dentry *dentry,
				   struct dentry *base,
				   unsigned int follow)
{
	vnode_t	*vp;
	uio_t	uio;
	iovec_t	iov;
	int	error = 0;
	char	*link = kmalloc(MAXNAMELEN+1, GFP_KERNEL); 

	vp = LINVFS_GET_VP(dentry->d_inode);
	iov.iov_base = link;
	iov.iov_len = MAXNAMELEN;

	uio.uio_iov = &iov;
	uio.uio_offset = 0;
	uio.uio_segflg = UIO_SYSSPACE;
	uio.uio_resid = MAXNAMELEN;

	VOP_READLINK(vp, &uio, sys_cred, error);
	if (error) {
		kfree_s(link, MAXNAMELEN);
		return NULL;
	}

	link[MAXNAMELEN - uio.uio_resid] = '\0';

	UPDATE_ATIME(dentry->d_inode);
	base = lookup_dentry(link, base, follow);
	kfree_s(link, MAXNAMELEN);
	return base;
}

/*
 * The following is used to get a list of blocks of the given
 * size from a pagebuf_bmap_t returned by VOP_BMAP.
 */

static void
_linvfs_set_blocks(
	int		*blocks,	/* Array of blocknos to fill in */
	int		nblocks,	/* number of elms in blocks array */
	pb_bmap_t	*pbmap,		/* mappings of blocks (big for XFS) */
	int		npbmaps,	/* number of pbmap entries */
	int		block_bits,	/* bits (max size) of blocks e.g. 9 for 512 */
	ssize_t		blocksize)	/* size of block */
{
	int i, *p, bmaps, blockno;

	i = nblocks;
        p = blocks;

	/* Zero out all the blocks, first */
	while(i) {
		*p++ = 0;
		i--;
	}

        i = nblocks;
	p = blocks;

	/*
	 * We need to walk the bmaps to cover the case where this page
	 * spans extents.
	 */

	for(bmaps = 0; bmaps < npbmaps && i; bmaps++, pbmap++) {

		/*
		 * While we have room in this bmap, bump the block number if real.
		 * If the block number is a hole or unwritten, make the blockno 0.
		 * This will cause a clearing of the associated memory.
		 */
		while(pbmap->pbm_bsize && i) {
			blockno = (long)pbmap->pbm_bn;
			if (pbmap->pbm_flags & (PBMF_HOLE|PBMF_UNWRITTEN)) {
				printk("_linvfs_set_blocks have HOLE or UNWRITTEN\n");
				blockno = 0;
			} else if (pbmap->pbm_offset) {
				if ( pbmap->pbm_offset % blocksize) {
					printk("_linvfs_set_blocks: this shouldn't happen %lld %d!\n",
					   pbmap->pbm_offset, blocksize);
				}
				blockno += pbmap->pbm_offset >> block_bits;
				pbmap->pbm_offset = 0;
				pbmap->pbm_bn = blockno;
			}

			*p = blockno;
			p++;
			i--;
			if (pbmap->pbm_bn > 0) {
				pbmap->pbm_bn++;
			}
			pbmap->pbm_bsize -= blocksize;
		}
        }
	return;
}


int linvfs_readpage(struct file *filp, struct page *page)
{
	int rval;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
	struct dentry	*dentry = filp->f_dentry;
	struct inode	*inode = dentry->d_inode;
	vnode_t		*vp;
	int		nr[PAGE_SIZE/512];
#define LINBMAP_MAX 4
	pb_bmap_t	pbmap[LINBMAP_MAX], *pbmapp = &pbmap[0];
	int		npbmaps = LINBMAP_MAX, map, pgoff, sz;
	int		error;

	atomic_inc(&page->count);
	set_bit(PG_locked, &page->flags);
	set_bit(PG_free_after, &page->flags);
	
	vp = LINVFS_GET_VP(inode);

	VOP_RWLOCK(vp, VRWLOCK_READ);
	VOP_BMAP(vp, page->offset, PAGE_SIZE, B_READ, pbmapp, &npbmaps, error);
	VOP_RWUNLOCK(vp, VRWLOCK_READ);

	if (error)
		return -EIO;

	/*
	 * While the page isn't done, move through the mappings either zero'ing
	 * out or reading real blocks. This will usually just deal with one
	 * one mapping, but ...
	 */

	pgoff = 0;
	sz = PAGE_SIZE;
	map = 0;
	while(sz > 0 && map < npbmaps) {
		int msize;

		if (pgoff) {
			printk("linvfs_readpage: iterating?? JIMJIM remove me %d\n",
				pgoff);
		}
		pbmapp = &pbmap[map];
		msize = pbmapp->pbm_bsize - pbmapp->pbm_offset;
		if (msize > (PAGE_SIZE - pgoff)) {
			msize = PAGE_SIZE - pgoff;
		}

		if (pbmapp->pbm_flags & (PBMF_HOLE|PBMF_UNWRITTEN)) {
			memset((void *)(page_address(page) + pgoff), 0, msize);
			set_bit(PG_uptodate, &page->flags);
			clear_bit(PG_locked, &page->flags);
			wake_up(&page->wait);
			rval = 0;
		} else {
			_linvfs_set_blocks(nr, PAGE_SIZE/512, pbmapp, npbmaps - map,
				inode->i_sb->s_blocksize_bits, inode->i_sb->s_blocksize);
	
			/* IO start */
			rval = brw_page(READ, page, inode->i_dev, nr,
				inode->i_sb->s_blocksize, 1);
		}
		sz -= msize;
		pgoff += msize;
		if (msize == pbmapp->pbm_bsize - pbmapp->pbm_offset) {
			map++;
		}
	}
#else
	rval = block_read_full_page(filp, page);
#endif
	return(rval);
}

int linvfs_writepage(struct file *filp, struct page *page)
{
	int rval;
	rval = -ENOSYS;
	printk("linvfs_writepage: NOT IMPLEMENTED\n");
	return(rval);
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
int linvfs_bmap(struct inode *inode, int block)
#else
int linvfs_get_block(struct inode *inode, long block, struct buffer_head *bh_result, int create)
#endif
{
	vnode_t		*vp;
	int		block_shift = inode->i_sb->s_blocksize_bits;
	off_t		offset = block << block_shift;
	ssize_t		count = inode->i_sb->s_blocksize;
	pb_bmap_t	pbmap;
	int		npbmaps = 1;
	int		error;
	long		blockno;

	if (block < 0) {
		printk(__FUNCTION__": called with block of -1\n");
		return -EIO;
	}

	vp = LINVFS_GET_VP(inode);

	VOP_RWLOCK(vp, VRWLOCK_READ);
	VOP_BMAP(vp, offset, count, B_READ,(struct page_buf_bmap_s *) &pbmap, &npbmaps, error);
	VOP_RWUNLOCK(vp, VRWLOCK_READ);
	if (error)
		return -EIO;
	/*
	 * JIMJIM This interface needs to be fixed to support 64 bit
	 * block numbers.
	 */
	
	blockno = (long)pbmap.pbm_bn;
	if (blockno < 0) return 0;
	if (pbmap.pbm_offset) {
		if ( pbmap.pbm_offset % count) {
			printk("linvfs_bmap: this shouldn't happen %lld %d!\n",
				pbmap.pbm_offset,
				count);
		}
		blockno += pbmap.pbm_offset >> block_shift;
	}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
	return(blockno >> (block_shift - 9));
#else
	if (!create) {
		bh_result->b_dev = inode->i_dev;
		bh_result->b_blocknr = blockno >> (block_shift - 9);
		bh_result->b_state |= (1UL << BH_Mapped);

		return 0;
	}

	return -EIO;
#endif
}

/*
 * JIMJIM have linvfs_updatepage use a new VOP if we are going to keep it
 * around. We might just want to use _pagebuf_file_write and the bmap, ...
 * instead of having this interface.
 */

int
linvfs_updatepage(struct file *filp, struct page *page, const char *buf,
	unsigned long offset, unsigned int bytes, int sync)
{
	vnode_t		*vp;
	struct dentry	*dentry = filp->f_dentry;
	struct inode	*inode = dentry->d_inode;
	unsigned	long block;
	int		*p, nr[PAGE_SIZE/512], error;
	int		wrote = 0, status, i, bmaps, block_bits;
	pb_bmap_t	pbmap[2];
	int		npbmaps = 2;
	long		blockno;

	/* printk("linvfs_updatepage(%s/%s %d@%ld, sync=%d)\n",
		dentry->d_parent->d_name.name, dentry->d_name.name,
		bytes, page->offset+offset, sync); */
	
	vp = LINVFS_GET_VP(inode);

	VOP_RWLOCK(vp, VRWLOCK_READ);
	VOP_BMAP(vp, page->offset, offset+bytes, B_WRITE,
		(struct page_buf_bmap_s *) &pbmap, &npbmaps, error);
	VOP_RWUNLOCK(vp, VRWLOCK_READ);
	if (error)
		return -EIO;

	atomic_inc(&page->count);

        set_bit(PG_locked, &page->flags);
        set_bit(PG_free_after, &page->flags);

	_linvfs_set_blocks(nr, PAGE_SIZE/512, &pbmap[0], npbmaps,
		inode->i_sb->s_blocksize_bits, inode->i_sb->s_blocksize);

	/* bytes can't be more than one page. */
	ASSERT(bytes <= PAGE_CACHE_SIZE);

	/*
	 * Clear the page up to starting offset in the page we
	 * are updating if that offset is beyond EOF.
	 * Otherwise, it has good data. We need to skip any good
	 * data within the page, too.
	 */

	if (offset && ((page->offset+offset) > inode->i_size)) {
		int good_bytes;

		/*
		 * Figure out how many bytes in the page are within
		 * the file and don't zero those.
		 */
		if (inode->i_size > page->offset) {
			good_bytes = inode->i_size - page->offset;
		} else {
			good_bytes = 0;
		}

		if (good_bytes >= PAGE_SIZE) {
			printk("linvfs_updatepage: NEW CODE no clear, good bytes %d\n",
				good_bytes);
		} else {
			memset((void *)(page_address(page)+good_bytes), 0, offset);
		}
	}

	/* Copy down the user's data */
	bytes -= copy_from_user((unsigned long *)(page_address(page) + offset),
						buf, bytes);
	

out_error:
	if (!bytes) {
		wrote = -EFAULT;
		clear_bit(PG_locked, &page->flags);
		clear_bit(PG_uptodate, &page->flags);
		goto out;
	}

	/* Now, write out the page to the right spot */
	status = brw_page(WRITE, page, inode->i_dev, nr, inode->i_sb->s_blocksize, 1);

	if (status < 0) /* Return error below if this failed */
		wrote = status;
	else
		wrote = bytes;

out:
	wake_up(&page->wait);
	return(wrote); /* The amount written */
}

/* Brute force approach for now - copy data into linux inode
 * from the results of a getattr. This gets called out of things
 * like stat.
 */
int linvfs_revalidate(struct dentry *dentry)
{
        struct inode *inode = dentry->d_inode;
        vnode_t *vp;
        vattr_t va;
        int     error;

        vp = LINVFS_GET_VP(inode);
        va.va_mask = AT_STAT;
        VOP_GETATTR(vp, &va, 0, sys_cred, error);

        inode->i_mode = VTTOIF(va.va_type) | va.va_mode;
        inode->i_nlink = va.va_nlink;
        inode->i_uid = va.va_uid;
        inode->i_gid = va.va_gid;
        inode->i_rdev = MKDEV(emajor(va.va_rdev), eminor(va.va_rdev));
        inode->i_size = va.va_size;
        inode->i_blocks = va.va_nblocks;
        inode->i_blksize = va.va_blksize;
        inode->i_atime = va.va_atime.tv_sec;
        inode->i_mtime = va.va_mtime.tv_sec;
        inode->i_ctime = va.va_ctime.tv_sec;

        return 0;
}

int
linvfs_pb_bmap(struct inode *inode, 
			   loff_t offset,
			   size_t count,
			   pb_bmap_t *pbmapp,
			   int maxpbbm, 
			   int *retpbbm, 
			   int flags/* page_buf_flags_t */)
{
	vnode_t		*vp;
	int		block_shift = inode->i_sb->s_blocksize_bits;
	pb_bmap_t	pbmap;
	int		npbmaps = 1;
	int		error, vop_flags;
	long		blockno;

	/*
	 * First map the flags into vop_flags.
	 */

	switch(flags) {
	case PBF_READ:
		vop_flags = B_READ;
		break;

	case PBF_WRITE:
		vop_flags = B_WRITE;
		break;
	
	default:
		printk("linvfs_pb_bmap: flag %x not implemented\n", flags);
		return -EINVAL;
	}

	vp = LINVFS_GET_VP(inode);

	*retpbbm = maxpbbm;

	VOP_RWLOCK(vp, VRWLOCK_READ);
	VOP_BMAP(vp, offset, count, vop_flags,
			(struct page_buf_bmap_s *) pbmapp, retpbbm, error);
	VOP_RWUNLOCK(vp, VRWLOCK_READ);

	return error;
}

struct inode_operations linvfs_file_inode_operations =
{
  &linvfs_file_operations,
  NULL,	 /*  create  */
  NULL,	 /*  lookup  */
  NULL,	 /*  link  */
  NULL,	 /*  unlink  */
  NULL,	 /*  symlink  */
  NULL,	 /*  mkdir  */
  NULL,	 /*  rmdir  */
  NULL,	 /*  mknod  */
  NULL,	 /*  rename  */
  NULL,	 /*  readlink  */
  NULL,	 /*  follow_link  */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
  linvfs_readpage,
  linvfs_writepage,
  linvfs_bmap,
#else
  linvfs_get_block,
  linvfs_readpage,
  linvfs_writepage,
  block_flushpage,
#endif
  NULL,	 /*  truncate  */
  NULL,  /*  permission  */
  NULL,	 /*  smap  */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
  linvfs_updatepage,  /*  updatepage  */
#endif
  linvfs_revalidate
#if defined(_USING_BUF_T)
  , linvfs_pb_bmap
#endif
};

struct inode_operations linvfs_dir_inode_operations =
{
  &linvfs_dir_operations,
  linvfs_create,
  linvfs_lookup,
  linvfs_link,	
  linvfs_unlink,	
  linvfs_symlink,	
  linvfs_mkdir,	
  linvfs_rmdir,	
  linvfs_mknod,	
  linvfs_rename,	
  NULL,	 /*  readlink  */
  NULL,	 /*  follow_link  */
  NULL,	 /*  readpage  */
  NULL,	 /*  writepage  */
  NULL,	 /*  bmap  */
  NULL,	 /*  truncate  */
  NULL,  /*  permission  */
  NULL,  /*  smap  */
  NULL,  /*  updatepage  */
  linvfs_revalidate
};

struct inode_operations linvfs_symlink_inode_operations =
{
  NULL,  /*  struct file_operations  */
  NULL,	 /*  create  */
  NULL,	 /*  lookup  */
  NULL,	 /*  link  */
  NULL,	 /*  unlink  */
  NULL,	 /*  symlink  */
  NULL,	 /*  mkdir  */
  NULL,	 /*  rmdir  */
  NULL,	 /*  mknod  */
  NULL,	 /*  rename  */
  linvfs_readlink,
  linvfs_follow_link,
  NULL,	 /*  readpage  */
  NULL,	 /*  writepage  */
  NULL,	 /*  bmap  */
  NULL,	 /*  truncate  */
  NULL,	 /*  permission  */
  NULL,	 /*  smap  */
  NULL,  /*  updatepage  */
  linvfs_revalidate
};