| /* | |
| * Copyright (C) Sistina Software, Inc. 1997-2003 All rights reserved. | |
| * Copyright (C) 2004-2006 Red Hat, Inc. All rights reserved. | |
| * | |
| * This copyrighted material is made available to anyone wishing to use, | |
| * modify, copy, or redistribute it subject to the terms and conditions | |
| * of the GNU General Public License version 2. | |
| */ | |
| #include <linux/sched.h> | |
| #include <linux/slab.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/completion.h> | |
| #include <linux/buffer_head.h> | |
| #include <linux/gfs2_ondisk.h> | |
| #include <linux/crc32.h> | |
| #include <linux/lm_interface.h> | |
| #include "gfs2.h" | |
| #include "incore.h" | |
| #include "dir.h" | |
| #include "glock.h" | |
| #include "glops.h" | |
| #include "inode.h" | |
| #include "ops_export.h" | |
| #include "rgrp.h" | |
| #include "util.h" | |
| static struct dentry *gfs2_decode_fh(struct super_block *sb, | |
| __u32 *fh, | |
| int fh_len, | |
| int fh_type, | |
| int (*acceptable)(void *context, | |
| struct dentry *dentry), | |
| void *context) | |
| { | |
| struct gfs2_fh_obj fh_obj; | |
| struct gfs2_inum *this, parent; | |
| if (fh_type != fh_len) | |
| return NULL; | |
| this = &fh_obj.this; | |
| fh_obj.imode = DT_UNKNOWN; | |
| memset(&parent, 0, sizeof(struct gfs2_inum)); | |
| switch (fh_type) { | |
| case GFS2_LARGE_FH_SIZE: | |
| parent.no_formal_ino = ((u64)be32_to_cpu(fh[4])) << 32; | |
| parent.no_formal_ino |= be32_to_cpu(fh[5]); | |
| parent.no_addr = ((u64)be32_to_cpu(fh[6])) << 32; | |
| parent.no_addr |= be32_to_cpu(fh[7]); | |
| fh_obj.imode = be32_to_cpu(fh[8]); | |
| case GFS2_SMALL_FH_SIZE: | |
| this->no_formal_ino = ((u64)be32_to_cpu(fh[0])) << 32; | |
| this->no_formal_ino |= be32_to_cpu(fh[1]); | |
| this->no_addr = ((u64)be32_to_cpu(fh[2])) << 32; | |
| this->no_addr |= be32_to_cpu(fh[3]); | |
| break; | |
| default: | |
| return NULL; | |
| } | |
| return gfs2_export_ops.find_exported_dentry(sb, &fh_obj, &parent, | |
| acceptable, context); | |
| } | |
| static int gfs2_encode_fh(struct dentry *dentry, __u32 *fh, int *len, | |
| int connectable) | |
| { | |
| struct inode *inode = dentry->d_inode; | |
| struct super_block *sb = inode->i_sb; | |
| struct gfs2_inode *ip = GFS2_I(inode); | |
| if (*len < GFS2_SMALL_FH_SIZE || | |
| (connectable && *len < GFS2_LARGE_FH_SIZE)) | |
| return 255; | |
| fh[0] = ip->i_num.no_formal_ino >> 32; | |
| fh[0] = cpu_to_be32(fh[0]); | |
| fh[1] = ip->i_num.no_formal_ino & 0xFFFFFFFF; | |
| fh[1] = cpu_to_be32(fh[1]); | |
| fh[2] = ip->i_num.no_addr >> 32; | |
| fh[2] = cpu_to_be32(fh[2]); | |
| fh[3] = ip->i_num.no_addr & 0xFFFFFFFF; | |
| fh[3] = cpu_to_be32(fh[3]); | |
| *len = GFS2_SMALL_FH_SIZE; | |
| if (!connectable || inode == sb->s_root->d_inode) | |
| return *len; | |
| spin_lock(&dentry->d_lock); | |
| inode = dentry->d_parent->d_inode; | |
| ip = GFS2_I(inode); | |
| igrab(inode); | |
| spin_unlock(&dentry->d_lock); | |
| fh[4] = ip->i_num.no_formal_ino >> 32; | |
| fh[4] = cpu_to_be32(fh[4]); | |
| fh[5] = ip->i_num.no_formal_ino & 0xFFFFFFFF; | |
| fh[5] = cpu_to_be32(fh[5]); | |
| fh[6] = ip->i_num.no_addr >> 32; | |
| fh[6] = cpu_to_be32(fh[6]); | |
| fh[7] = ip->i_num.no_addr & 0xFFFFFFFF; | |
| fh[7] = cpu_to_be32(fh[7]); | |
| fh[8] = cpu_to_be32(inode->i_mode); | |
| fh[9] = 0; /* pad to double word */ | |
| *len = GFS2_LARGE_FH_SIZE; | |
| iput(inode); | |
| return *len; | |
| } | |
| struct get_name_filldir { | |
| struct gfs2_inum inum; | |
| char *name; | |
| }; | |
| static int get_name_filldir(void *opaque, const char *name, unsigned int length, | |
| u64 offset, struct gfs2_inum *inum, | |
| unsigned int type) | |
| { | |
| struct get_name_filldir *gnfd = (struct get_name_filldir *)opaque; | |
| if (!gfs2_inum_equal(inum, &gnfd->inum)) | |
| return 0; | |
| memcpy(gnfd->name, name, length); | |
| gnfd->name[length] = 0; | |
| return 1; | |
| } | |
| static int gfs2_get_name(struct dentry *parent, char *name, | |
| struct dentry *child) | |
| { | |
| struct inode *dir = parent->d_inode; | |
| struct inode *inode = child->d_inode; | |
| struct gfs2_inode *dip, *ip; | |
| struct get_name_filldir gnfd; | |
| struct gfs2_holder gh; | |
| u64 offset = 0; | |
| int error; | |
| if (!dir) | |
| return -EINVAL; | |
| if (!S_ISDIR(dir->i_mode) || !inode) | |
| return -EINVAL; | |
| dip = GFS2_I(dir); | |
| ip = GFS2_I(inode); | |
| *name = 0; | |
| gnfd.inum = ip->i_num; | |
| gnfd.name = name; | |
| error = gfs2_glock_nq_init(dip->i_gl, LM_ST_SHARED, 0, &gh); | |
| if (error) | |
| return error; | |
| error = gfs2_dir_read(dir, &offset, &gnfd, get_name_filldir); | |
| gfs2_glock_dq_uninit(&gh); | |
| if (!error && !*name) | |
| error = -ENOENT; | |
| return error; | |
| } | |
| static struct dentry *gfs2_get_parent(struct dentry *child) | |
| { | |
| struct qstr dotdot; | |
| struct inode *inode; | |
| struct dentry *dentry; | |
| gfs2_str2qstr(&dotdot, ".."); | |
| inode = gfs2_lookupi(child->d_inode, &dotdot, 1, NULL); | |
| if (!inode) | |
| return ERR_PTR(-ENOENT); | |
| /* | |
| * In case of an error, @inode carries the error value, and we | |
| * have to return that as a(n invalid) pointer to dentry. | |
| */ | |
| if (IS_ERR(inode)) | |
| return ERR_PTR(PTR_ERR(inode)); | |
| dentry = d_alloc_anon(inode); | |
| if (!dentry) { | |
| iput(inode); | |
| return ERR_PTR(-ENOMEM); | |
| } | |
| return dentry; | |
| } | |
| static struct dentry *gfs2_get_dentry(struct super_block *sb, void *inum_obj) | |
| { | |
| struct gfs2_sbd *sdp = sb->s_fs_info; | |
| struct gfs2_fh_obj *fh_obj = (struct gfs2_fh_obj *)inum_obj; | |
| struct gfs2_inum *inum = &fh_obj->this; | |
| struct gfs2_holder i_gh, ri_gh, rgd_gh; | |
| struct gfs2_rgrpd *rgd; | |
| struct inode *inode; | |
| struct dentry *dentry; | |
| int error; | |
| /* System files? */ | |
| inode = gfs2_ilookup(sb, inum); | |
| if (inode) { | |
| if (GFS2_I(inode)->i_num.no_formal_ino != inum->no_formal_ino) { | |
| iput(inode); | |
| return ERR_PTR(-ESTALE); | |
| } | |
| goto out_inode; | |
| } | |
| error = gfs2_glock_nq_num(sdp, inum->no_addr, &gfs2_inode_glops, | |
| LM_ST_SHARED, LM_FLAG_ANY | GL_LOCAL_EXCL, | |
| &i_gh); | |
| if (error) | |
| return ERR_PTR(error); | |
| error = gfs2_rindex_hold(sdp, &ri_gh); | |
| if (error) | |
| goto fail; | |
| error = -EINVAL; | |
| rgd = gfs2_blk2rgrpd(sdp, inum->no_addr); | |
| if (!rgd) | |
| goto fail_rindex; | |
| error = gfs2_glock_nq_init(rgd->rd_gl, LM_ST_SHARED, 0, &rgd_gh); | |
| if (error) | |
| goto fail_rindex; | |
| error = -ESTALE; | |
| if (gfs2_get_block_type(rgd, inum->no_addr) != GFS2_BLKST_DINODE) | |
| goto fail_rgd; | |
| gfs2_glock_dq_uninit(&rgd_gh); | |
| gfs2_glock_dq_uninit(&ri_gh); | |
| inode = gfs2_inode_lookup(sb, inum, fh_obj->imode); | |
| if (!inode) | |
| goto fail; | |
| if (IS_ERR(inode)) { | |
| error = PTR_ERR(inode); | |
| goto fail; | |
| } | |
| error = gfs2_inode_refresh(GFS2_I(inode)); | |
| if (error) { | |
| iput(inode); | |
| goto fail; | |
| } | |
| error = -EIO; | |
| if (GFS2_I(inode)->i_di.di_flags & GFS2_DIF_SYSTEM) { | |
| iput(inode); | |
| goto fail; | |
| } | |
| gfs2_glock_dq_uninit(&i_gh); | |
| out_inode: | |
| dentry = d_alloc_anon(inode); | |
| if (!dentry) { | |
| iput(inode); | |
| return ERR_PTR(-ENOMEM); | |
| } | |
| return dentry; | |
| fail_rgd: | |
| gfs2_glock_dq_uninit(&rgd_gh); | |
| fail_rindex: | |
| gfs2_glock_dq_uninit(&ri_gh); | |
| fail: | |
| gfs2_glock_dq_uninit(&i_gh); | |
| return ERR_PTR(error); | |
| } | |
| struct export_operations gfs2_export_ops = { | |
| .decode_fh = gfs2_decode_fh, | |
| .encode_fh = gfs2_encode_fh, | |
| .get_name = gfs2_get_name, | |
| .get_parent = gfs2_get_parent, | |
| .get_dentry = gfs2_get_dentry, | |
| }; | |