File: [Development] / xfs-linux-nodel / linux-2.6 / xfs_iops.c (download)
Revision 1.33, Fri Jun 9 02:24:43 2000 UTC (17 years, 4 months ago) by ananth
Branch: MAIN
Changes since 1.32: +5 -5
lines
Merge of 2.3.99pre2-xfs:slinx:55532a by ananth.
Merge of 2.3.42-xfs:slinx:55532a by ananth.
Fix a bug in rmdir where the inode was not being
released properly. This leads to bizzare cases
where the (already removed) inode for the directory
being used for normal files, resulting in EISDIR
during I/O operations on that normal file.
|
/*
* 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 <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/vfs.h>
#include <sys/pvfs.h>
#include <sys/vnode.h>
#include <ksys/behavior.h>
#include <sys/mode.h>
#include <linux/xfs_linux.h>
#include <linux/xfs_cred.h>
#include <linux/xfs_file.h>
#include <linux/page_buf.h>
#include <xfs_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;
cred_t cred; /* Temporary cred workaround */
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,
&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,
&cred, error);
} else if (tp == VDIR) {
VOP_MKDIR(dvp, (char *)dentry->d_name.name, &va, &vp,
&cred, error);
}
if (!error) {
ASSERT(vp);
bzero(&va, sizeof(va));
va.va_mask = AT_NODEID;
VOP_GETATTR(vp, &va, 0, &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;
cred_t cred; /* Temporary cred workaround */
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,
&cred, error);
if (!error) {
ASSERT(cvp);
bzero(&va, sizeof(va));
va.va_mask = AT_NODEID;
VOP_GETATTR(cvp, &va, 0, &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 */
cred_t cred; /* Temporary cred workaround */
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, &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 */
cred_t cred; /* Temporary cred workaround */
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, &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;
cred_t cred; /* Temporary cred workaround */
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,
&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,
&cred, error);
if (!error) {
ASSERT(cvp);
bzero(&va, sizeof(va));
va.va_mask = AT_NODEID;
VOP_GETATTR(cvp, &va, 0, &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 */
cred_t cred; /* Temporary cred workaround */
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, &cred, error);
if (!error) {
struct inode *inode = dentry->d_inode;
inode->i_nlink = 0;
inode->i_size = 0;
inode->i_version = ++event;
mark_inode_dirty(inode);
dir->i_nlink--;
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;
cred_t cred; /* Temporary cred workaround */
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, &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;
cred_t cred; /* Temporary cred workaround */
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, &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);
cred_t cred; /* Temporary cred workaround */
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, &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;
}
int linvfs_get_block(struct inode *inode, long block, struct buffer_head *bh_result, int create)
{
vnode_t *vp;
int block_shift = inode->i_sb->s_blocksize_bits;
loff_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, XFS_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 %Ld %d!\n",
pbmap.pbm_offset,
count);
}
blockno += pbmap.pbm_offset >> block_shift;
}
if (!create) {
bh_result->b_blocknr = blockno >> (block_shift - 9);
bh_result->b_state |= (1UL << BH_Mapped);
return 0;
}
return -EIO;
}
int linvfs_permission(struct inode *ip, int mode)
{
cred_t cred; /* Temporary cred workaround */
vnode_t *vp;
int error;
mode <<= 6;
vp = LINVFS_GET_VP(ip);
VOP_ACCESS(vp, mode, &cred, error);
return error;
}
/* 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;
cred_t cred; /* Temporary cred workaround */
vp = LINVFS_GET_VP(inode);
va.va_mask = AT_STAT;
VOP_GETATTR(vp, &va, 0, &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 >> (PAGE_SHIFT - 9);
inode->i_blksize = PAGE_SIZE;
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,
struct page_buf_bmap_s *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 = XFS_B_READ;
break;
case PBF_WRITE:
vop_flags = XFS_B_WRITE;
break;
default:
printk("linvfs_pb_bmap: flag %x not implemented\n", flags);
return -EINVAL;
}
vp = LINVFS_GET_VP(inode);
*retpbbm = maxpbbm;
VOP_BMAP(vp, offset, count, vop_flags,
(struct page_buf_bmap_s *) pbmapp, retpbbm, error);
return error;
}
struct inode_operations linvfs_file_inode_operations =
{
default_file_ops: &linvfs_file_operations,
get_block: linvfs_get_block,
readpage: pagebuf_read_full_page,
writepage: pagebuf_write_full_page,
permission: linvfs_permission,
revalidate: linvfs_revalidate,
pagebuf_bmap: linvfs_pb_bmap,
pagebuf_fileread: pagebuf_file_read,
};
struct inode_operations linvfs_dir_inode_operations =
{
default_file_ops: &linvfs_dir_operations,
create: linvfs_create,
lookup: linvfs_lookup,
link: linvfs_link,
unlink: linvfs_unlink,
symlink: linvfs_symlink,
mkdir: linvfs_mkdir,
rmdir: linvfs_rmdir,
mknod: linvfs_mknod,
rename: linvfs_rename,
permission: linvfs_permission,
revalidate: linvfs_revalidate
};
struct inode_operations linvfs_symlink_inode_operations =
{
readlink: linvfs_readlink,
follow_link: linvfs_follow_link,
permission: linvfs_permission,
revalidate: linvfs_revalidate
};