1/*
2 * @TAG(OTHER_GPL)
3 */
4
5/* fs/fat/nfs.c
6 *
7 * This software is licensed under the terms of the GNU General Public
8 * License version 2, as published by the Free Software Foundation, and
9 * may be copied, distributed, and modified under those terms.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 */
17
18#include <linux/exportfs.h>
19#include "fat.h"
20
21struct fat_fid {
22	u32 i_gen;
23	u32 i_pos_low;
24	u16 i_pos_hi;
25	u16 parent_i_pos_hi;
26	u32 parent_i_pos_low;
27	u32 parent_i_gen;
28};
29
30#define FAT_FID_SIZE_WITHOUT_PARENT 3
31#define FAT_FID_SIZE_WITH_PARENT (sizeof(struct fat_fid)/sizeof(u32))
32
33/**
34 * Look up a directory inode given its starting cluster.
35 */
36static struct inode *fat_dget(struct super_block *sb, int i_logstart)
37{
38	struct msdos_sb_info *sbi = MSDOS_SB(sb);
39	struct hlist_head *head;
40	struct msdos_inode_info *i;
41	struct inode *inode = NULL;
42
43	head = sbi->dir_hashtable + fat_dir_hash(i_logstart);
44	spin_lock(&sbi->dir_hash_lock);
45	hlist_for_each_entry(i, head, i_dir_hash) {
46		BUG_ON(i->vfs_inode.i_sb != sb);
47		if (i->i_logstart != i_logstart)
48			continue;
49		inode = igrab(&i->vfs_inode);
50		if (inode)
51			break;
52	}
53	spin_unlock(&sbi->dir_hash_lock);
54	return inode;
55}
56
57static struct inode *fat_ilookup(struct super_block *sb, u64 ino, loff_t i_pos)
58{
59	if (MSDOS_SB(sb)->options.nfs == FAT_NFS_NOSTALE_RO)
60		return fat_iget(sb, i_pos);
61
62	else {
63		if ((ino < MSDOS_ROOT_INO) || (ino == MSDOS_FSINFO_INO))
64			return NULL;
65		return ilookup(sb, ino);
66	}
67}
68
69static struct inode *__fat_nfs_get_inode(struct super_block *sb,
70				       u64 ino, u32 generation, loff_t i_pos)
71{
72	struct inode *inode = fat_ilookup(sb, ino, i_pos);
73
74	if (inode && generation && (inode->i_generation != generation)) {
75		iput(inode);
76		inode = NULL;
77	}
78	if (inode == NULL && MSDOS_SB(sb)->options.nfs == FAT_NFS_NOSTALE_RO) {
79		struct buffer_head *bh = NULL;
80		struct msdos_dir_entry *de ;
81		sector_t blocknr;
82		int offset;
83		fat_get_blknr_offset(MSDOS_SB(sb), i_pos, &blocknr, &offset);
84		bh = sb_bread(sb, blocknr);
85		if (!bh) {
86			fat_msg(sb, KERN_ERR,
87				"unable to read block(%llu) for building NFS inode",
88				(llu)blocknr);
89			return inode;
90		}
91		de = (struct msdos_dir_entry *)bh->b_data;
92		/* If a file is deleted on server and client is not updated
93		 * yet, we must not build the inode upon a lookup call.
94		 */
95		if (IS_FREE(de[offset].name))
96			inode = NULL;
97		else
98			inode = fat_build_inode(sb, &de[offset], i_pos);
99		brelse(bh);
100	}
101
102	return inode;
103}
104
105static struct inode *fat_nfs_get_inode(struct super_block *sb,
106				       u64 ino, u32 generation)
107{
108
109	return __fat_nfs_get_inode(sb, ino, generation, 0);
110}
111
112static int
113fat_encode_fh_nostale(struct inode *inode, __u32 *fh, int *lenp,
114		      struct inode *parent)
115{
116	int len = *lenp;
117	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
118	struct fat_fid *fid = (struct fat_fid *) fh;
119	loff_t i_pos;
120	int type = FILEID_FAT_WITHOUT_PARENT;
121
122	if (parent) {
123		if (len < FAT_FID_SIZE_WITH_PARENT) {
124			*lenp = FAT_FID_SIZE_WITH_PARENT;
125			return FILEID_INVALID;
126		}
127	} else {
128		if (len < FAT_FID_SIZE_WITHOUT_PARENT) {
129			*lenp = FAT_FID_SIZE_WITHOUT_PARENT;
130			return FILEID_INVALID;
131		}
132	}
133
134	i_pos = fat_i_pos_read(sbi, inode);
135	*lenp = FAT_FID_SIZE_WITHOUT_PARENT;
136	fid->i_gen = inode->i_generation;
137	fid->i_pos_low = i_pos & 0xFFFFFFFF;
138	fid->i_pos_hi = (i_pos >> 32) & 0xFFFF;
139	if (parent) {
140		i_pos = fat_i_pos_read(sbi, parent);
141		fid->parent_i_pos_hi = (i_pos >> 32) & 0xFFFF;
142		fid->parent_i_pos_low = i_pos & 0xFFFFFFFF;
143		fid->parent_i_gen = parent->i_generation;
144		type = FILEID_FAT_WITH_PARENT;
145		*lenp = FAT_FID_SIZE_WITH_PARENT;
146	}
147
148	return type;
149}
150
151/**
152 * Map a NFS file handle to a corresponding dentry.
153 * The dentry may or may not be connected to the filesystem root.
154 */
155static struct dentry *fat_fh_to_dentry(struct super_block *sb, struct fid *fid,
156				int fh_len, int fh_type)
157{
158	return generic_fh_to_dentry(sb, fid, fh_len, fh_type,
159				    fat_nfs_get_inode);
160}
161
162static struct dentry *fat_fh_to_dentry_nostale(struct super_block *sb,
163					       struct fid *fh, int fh_len,
164					       int fh_type)
165{
166	struct inode *inode = NULL;
167	struct fat_fid *fid = (struct fat_fid *)fh;
168	loff_t i_pos;
169
170	switch (fh_type) {
171	case FILEID_FAT_WITHOUT_PARENT:
172		if (fh_len < FAT_FID_SIZE_WITHOUT_PARENT)
173			return NULL;
174		break;
175	case FILEID_FAT_WITH_PARENT:
176		if (fh_len < FAT_FID_SIZE_WITH_PARENT)
177			return NULL;
178		break;
179	default:
180		return NULL;
181	}
182	i_pos = fid->i_pos_hi;
183	i_pos = (i_pos << 32) | (fid->i_pos_low);
184	inode = __fat_nfs_get_inode(sb, 0, fid->i_gen, i_pos);
185
186	return d_obtain_alias(inode);
187}
188
189/*
190 * Find the parent for a file specified by NFS handle.
191 * This requires that the handle contain the i_ino of the parent.
192 */
193static struct dentry *fat_fh_to_parent(struct super_block *sb, struct fid *fid,
194				int fh_len, int fh_type)
195{
196	return generic_fh_to_parent(sb, fid, fh_len, fh_type,
197				    fat_nfs_get_inode);
198}
199
200static struct dentry *fat_fh_to_parent_nostale(struct super_block *sb,
201					       struct fid *fh, int fh_len,
202					       int fh_type)
203{
204	struct inode *inode = NULL;
205	struct fat_fid *fid = (struct fat_fid *)fh;
206	loff_t i_pos;
207
208	if (fh_len < FAT_FID_SIZE_WITH_PARENT)
209		return NULL;
210
211	switch (fh_type) {
212	case FILEID_FAT_WITH_PARENT:
213		i_pos = fid->parent_i_pos_hi;
214		i_pos = (i_pos << 32) | (fid->parent_i_pos_low);
215		inode = __fat_nfs_get_inode(sb, 0, fid->parent_i_gen, i_pos);
216		break;
217	}
218
219	return d_obtain_alias(inode);
220}
221
222/*
223 * Rebuild the parent for a directory that is not connected
224 *  to the filesystem root
225 */
226static
227struct inode *fat_rebuild_parent(struct super_block *sb, int parent_logstart)
228{
229	int search_clus, clus_to_match;
230	struct msdos_dir_entry *de;
231	struct inode *parent = NULL;
232	struct inode *dummy_grand_parent = NULL;
233	struct fat_slot_info sinfo;
234	struct msdos_sb_info *sbi = MSDOS_SB(sb);
235	sector_t blknr = fat_clus_to_blknr(sbi, parent_logstart);
236	struct buffer_head *parent_bh = sb_bread(sb, blknr);
237	if (!parent_bh) {
238		fat_msg(sb, KERN_ERR,
239			"unable to read cluster of parent directory");
240		return NULL;
241	}
242
243	de = (struct msdos_dir_entry *) parent_bh->b_data;
244	clus_to_match = fat_get_start(sbi, &de[0]);
245	search_clus = fat_get_start(sbi, &de[1]);
246
247	dummy_grand_parent = fat_dget(sb, search_clus);
248	if (!dummy_grand_parent) {
249		dummy_grand_parent = new_inode(sb);
250		if (!dummy_grand_parent) {
251			brelse(parent_bh);
252			return parent;
253		}
254
255		dummy_grand_parent->i_ino = iunique(sb, MSDOS_ROOT_INO);
256		fat_fill_inode(dummy_grand_parent, &de[1]);
257		MSDOS_I(dummy_grand_parent)->i_pos = -1;
258	}
259
260	if (!fat_scan_logstart(dummy_grand_parent, clus_to_match, &sinfo))
261		parent = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
262
263	brelse(parent_bh);
264	iput(dummy_grand_parent);
265
266	return parent;
267}
268
269/*
270 * Find the parent for a directory that is not currently connected to
271 * the filesystem root.
272 *
273 * On entry, the caller holds lock on d_inode(child_dir)
274 */
275static struct dentry *fat_get_parent(struct dentry *child_dir)
276{
277	struct super_block *sb = child_dir->d_sb;
278	struct buffer_head *bh = NULL;
279	struct msdos_dir_entry *de;
280	struct inode *parent_inode = NULL;
281	struct msdos_sb_info *sbi = MSDOS_SB(sb);
282
283	if (!fat_get_dotdot_entry(d_inode(child_dir), &bh, &de)) {
284		int parent_logstart = fat_get_start(sbi, de);
285		parent_inode = fat_dget(sb, parent_logstart);
286		if (!parent_inode && sbi->options.nfs == FAT_NFS_NOSTALE_RO)
287			parent_inode = fat_rebuild_parent(sb, parent_logstart);
288	}
289	brelse(bh);
290
291	return d_obtain_alias(parent_inode);
292}
293
294const struct export_operations fat_export_ops = {
295	.fh_to_dentry   = fat_fh_to_dentry,
296	.fh_to_parent   = fat_fh_to_parent,
297	.get_parent     = fat_get_parent,
298};
299
300const struct export_operations fat_export_ops_nostale = {
301	.encode_fh      = fat_encode_fh_nostale,
302	.fh_to_dentry   = fat_fh_to_dentry_nostale,
303	.fh_to_parent   = fat_fh_to_parent_nostale,
304	.get_parent     = fat_get_parent,
305};
306