export.c 5.15 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0
Linus Torvalds's avatar
Linus Torvalds committed
2 3 4 5 6 7 8 9 10 11 12
/*
 * fs/isofs/export.c
 *
 *  (C) 2004  Paul Serice - The new inode scheme requires switching
 *                          from iget() to iget5_locked() which means
 *                          the NFS export operations have to be hand
 *                          coded because the default routines rely on
 *                          iget().
 *
 * The following files are helpful:
 *
13
 *     Documentation/filesystems/nfs/Exporting
Linus Torvalds's avatar
Linus Torvalds committed
14 15 16
 *     fs/exportfs/expfs.c.
 */

17
#include "isofs.h"
Linus Torvalds's avatar
Linus Torvalds committed
18 19 20 21 22 23 24 25

static struct dentry *
isofs_export_iget(struct super_block *sb,
		  unsigned long block,
		  unsigned long offset,
		  __u32 generation)
{
	struct inode *inode;
26

Linus Torvalds's avatar
Linus Torvalds committed
27 28 29
	if (block == 0)
		return ERR_PTR(-ESTALE);
	inode = isofs_iget(sb, block, offset);
30 31 32
	if (IS_ERR(inode))
		return ERR_CAST(inode);
	if (generation && inode->i_generation != generation) {
Linus Torvalds's avatar
Linus Torvalds committed
33 34 35
		iput(inode);
		return ERR_PTR(-ESTALE);
	}
36
	return d_obtain_alias(inode);
Linus Torvalds's avatar
Linus Torvalds committed
37 38 39 40 41 42 43 44 45 46 47
}

/* This function is surprisingly simple.  The trick is understanding
 * that "child" is always a directory. So, to find its parent, you
 * simply need to find its ".." entry, normalize its block and offset,
 * and return the underlying inode.  See the comments for
 * isofs_normalize_block_and_offset(). */
static struct dentry *isofs_export_get_parent(struct dentry *child)
{
	unsigned long parent_block = 0;
	unsigned long parent_offset = 0;
48
	struct inode *child_inode = d_inode(child);
Linus Torvalds's avatar
Linus Torvalds committed
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
	struct iso_inode_info *e_child_inode = ISOFS_I(child_inode);
	struct iso_directory_record *de = NULL;
	struct buffer_head * bh = NULL;
	struct dentry *rv = NULL;

	/* "child" must always be a directory. */
	if (!S_ISDIR(child_inode->i_mode)) {
		printk(KERN_ERR "isofs: isofs_export_get_parent(): "
		       "child is not a directory!\n");
		rv = ERR_PTR(-EACCES);
		goto out;
	}

	/* It is an invariant that the directory offset is zero.  If
	 * it is not zero, it means the directory failed to be
	 * normalized for some reason. */
	if (e_child_inode->i_iget5_offset != 0) {
		printk(KERN_ERR "isofs: isofs_export_get_parent(): "
		       "child directory not normalized!\n");
		rv = ERR_PTR(-EACCES);
		goto out;
	}

	/* The child inode has been normalized such that its
	 * i_iget5_block value points to the "." entry.  Fortunately,
	 * the ".." entry is located in the same block. */
	parent_block = e_child_inode->i_iget5_block;

	/* Get the block in question. */
	bh = sb_bread(child_inode->i_sb, parent_block);
	if (bh == NULL) {
		rv = ERR_PTR(-EACCES);
		goto out;
	}

	/* This is the "." entry. */
	de = (struct iso_directory_record*)bh->b_data;

	/* The ".." entry is always the second entry. */
	parent_offset = (unsigned long)isonum_711(de->length);
	de = (struct iso_directory_record*)(bh->b_data + parent_offset);

	/* Verify it is in fact the ".." entry. */
	if ((isonum_711(de->name_len) != 1) || (de->name[0] != 1)) {
		printk(KERN_ERR "isofs: Unable to find the \"..\" "
		       "directory for NFS.\n");
		rv = ERR_PTR(-EACCES);
		goto out;
	}

	/* Normalize */
	isofs_normalize_block_and_offset(de, &parent_block, &parent_offset);

102 103
	rv = d_obtain_alias(isofs_iget(child_inode->i_sb, parent_block,
				     parent_offset));
Linus Torvalds's avatar
Linus Torvalds committed
104
 out:
105
	if (bh)
Linus Torvalds's avatar
Linus Torvalds committed
106 107 108 109 110
		brelse(bh);
	return rv;
}

static int
Al Viro's avatar
Al Viro committed
111
isofs_export_encode_fh(struct inode *inode,
Linus Torvalds's avatar
Linus Torvalds committed
112 113
		       __u32 *fh32,
		       int *max_len,
Al Viro's avatar
Al Viro committed
114
		       struct inode *parent)
Linus Torvalds's avatar
Linus Torvalds committed
115 116 117 118 119 120 121 122 123 124 125 126
{
	struct iso_inode_info * ei = ISOFS_I(inode);
	int len = *max_len;
	int type = 1;
	__u16 *fh16 = (__u16*)fh32;

	/*
	 * WARNING: max_len is 5 for NFSv2.  Because of this
	 * limitation, we use the lower 16 bits of fh32[1] to hold the
	 * offset of the inode and the upper 16 bits of fh32[1] to
	 * hold the offset of the parent.
	 */
Al Viro's avatar
Al Viro committed
127
	if (parent && (len < 5)) {
128
		*max_len = 5;
129
		return FILEID_INVALID;
130 131
	} else if (len < 3) {
		*max_len = 3;
132
		return FILEID_INVALID;
133
	}
Linus Torvalds's avatar
Linus Torvalds committed
134 135 136 137

	len = 3;
	fh32[0] = ei->i_iget5_block;
 	fh16[2] = (__u16)ei->i_iget5_offset;  /* fh16 [sic] */
138
	fh16[3] = 0;  /* avoid leaking uninitialized data */
Linus Torvalds's avatar
Linus Torvalds committed
139
	fh32[2] = inode->i_generation;
Al Viro's avatar
Al Viro committed
140
	if (parent) {
Linus Torvalds's avatar
Linus Torvalds committed
141 142 143 144 145 146 147 148 149 150 151 152
		struct iso_inode_info *eparent;
		eparent = ISOFS_I(parent);
		fh32[3] = eparent->i_iget5_block;
		fh16[3] = (__u16)eparent->i_iget5_offset;  /* fh16 [sic] */
		fh32[4] = parent->i_generation;
		len = 5;
		type = 2;
	}
	*max_len = len;
	return type;
}

153 154 155 156 157 158 159 160
struct isofs_fid {
	u32 block;
	u16 offset;
	u16 parent_offset;
	u32 generation;
	u32 parent_block;
	u32 parent_generation;
};
Linus Torvalds's avatar
Linus Torvalds committed
161

162 163
static struct dentry *isofs_fh_to_dentry(struct super_block *sb,
	struct fid *fid, int fh_len, int fh_type)
Linus Torvalds's avatar
Linus Torvalds committed
164
{
165
	struct isofs_fid *ifid = (struct isofs_fid *)fid;
Linus Torvalds's avatar
Linus Torvalds committed
166

167
	if (fh_len < 3 || fh_type > 2)
Linus Torvalds's avatar
Linus Torvalds committed
168 169
		return NULL;

170 171
	return isofs_export_iget(sb, ifid->block, ifid->offset,
			ifid->generation);
Linus Torvalds's avatar
Linus Torvalds committed
172 173
}

174 175 176 177 178
static struct dentry *isofs_fh_to_parent(struct super_block *sb,
		struct fid *fid, int fh_len, int fh_type)
{
	struct isofs_fid *ifid = (struct isofs_fid *)fid;

179
	if (fh_len < 2 || fh_type != 2)
180 181 182 183 184 185 186
		return NULL;

	return isofs_export_iget(sb,
			fh_len > 2 ? ifid->parent_block : 0,
			ifid->parent_offset,
			fh_len > 4 ? ifid->parent_generation : 0);
}
Linus Torvalds's avatar
Linus Torvalds committed
187

188
const struct export_operations isofs_export_ops = {
Linus Torvalds's avatar
Linus Torvalds committed
189
	.encode_fh	= isofs_export_encode_fh,
190 191
	.fh_to_dentry	= isofs_fh_to_dentry,
	.fh_to_parent	= isofs_fh_to_parent,
Linus Torvalds's avatar
Linus Torvalds committed
192 193
	.get_parent     = isofs_export_get_parent,
};