summaryrefslogtreecommitdiffstats
path: root/fs/unionfs/dentry.c
blob: 85b5d3c36d50a16fb0fc98fe31fe992e9b06f477 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
/*
 * Copyright (c) 2003-2009 Erez Zadok
 * Copyright (c) 2003-2006 Charles P. Wright
 * Copyright (c) 2005-2007 Josef 'Jeff' Sipek
 * Copyright (c) 2005-2006 Junjiro Okajima
 * Copyright (c) 2005      Arun M. Krishnakumar
 * Copyright (c) 2004-2006 David P. Quigley
 * Copyright (c) 2003-2004 Mohammad Nayyer Zubair
 * Copyright (c) 2003      Puja Gupta
 * Copyright (c) 2003      Harikesavan Krishnan
 * Copyright (c) 2003-2009 Stony Brook University
 * Copyright (c) 2003-2009 The Research Foundation of SUNY
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include "union.h"

bool is_negative_lower(const struct dentry *dentry)
{
	int bindex;
	struct dentry *lower_dentry;

	BUG_ON(!dentry);
	/* cache coherency: check if file was deleted on lower branch */
	if (dbstart(dentry) < 0)
		return true;
	for (bindex = dbstart(dentry); bindex <= dbend(dentry); bindex++) {
		lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
		/* unhashed (i.e., unlinked) lower dentries don't count */
		if (lower_dentry && lower_dentry->d_inode &&
		    !d_deleted(lower_dentry) &&
		    !(lower_dentry->d_flags & DCACHE_NFSFS_RENAMED))
			return false;
	}
	return true;
}

static inline void __dput_lowers(struct dentry *dentry, int start, int end)
{
	struct dentry *lower_dentry;
	int bindex;

	if (start < 0)
		return;
	for (bindex = start; bindex <= end; bindex++) {
		lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
		if (!lower_dentry)
			continue;
		unionfs_set_lower_dentry_idx(dentry, bindex, NULL);
		dput(lower_dentry);
	}
}

/*
 * Purge and invalidate as many data pages of a unionfs inode.  This is
 * called when the lower inode has changed, and we want to force processes
 * to re-get the new data.
 */
static inline void purge_inode_data(struct inode *inode)
{
	/* remove all non-private mappings */
	unmap_mapping_range(inode->i_mapping, 0, 0, 0);
	/* invalidate as many pages as possible */
	invalidate_mapping_pages(inode->i_mapping, 0, -1);
	/*
	 * Don't try to truncate_inode_pages here, because this could lead
	 * to a deadlock between some of address_space ops and dentry
	 * revalidation: the address space op is invoked with a lock on our
	 * own page, and truncate_inode_pages will block on locked pages.
	 */
}

/*
 * Revalidate a single file/symlink/special dentry.  Assume that info nodes
 * of the @dentry and its @parent are locked.  Assume parent is valid,
 * otherwise return false (and let's hope the VFS will try to re-lookup this
 * dentry).  Returns true if valid, false otherwise.
 */
bool __unionfs_d_revalidate(struct dentry *dentry, struct dentry *parent,
			    bool willwrite)
{
	bool valid = true;	/* default is valid */
	struct dentry *lower_dentry;
	struct dentry *result;
	int bindex, bstart, bend;
	int sbgen, dgen, pdgen;
	int positive = 0;
	int interpose_flag;

	verify_locked(dentry);
	verify_locked(parent);

	/* if the dentry is unhashed, do NOT revalidate */
	if (d_deleted(dentry))
		goto out;

	dgen = atomic_read(&UNIONFS_D(dentry)->generation);

	if (is_newer_lower(dentry)) {
		/* root dentry is always valid */
		if (IS_ROOT(dentry)) {
			unionfs_copy_attr_times(dentry->d_inode);
		} else {
			/*
			 * reset generation number to zero, guaranteed to be
			 * "old"
			 */
			dgen = 0;
			atomic_set(&UNIONFS_D(dentry)->generation, dgen);
		}
		if (!willwrite)
			purge_inode_data(dentry->d_inode);
	}

	sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation);

	BUG_ON(dbstart(dentry) == -1);
	if (dentry->d_inode)
		positive = 1;

	/* if our dentry is valid, then validate all lower ones */
	if (sbgen == dgen)
		goto validate_lowers;

	/* The root entry should always be valid */
	BUG_ON(IS_ROOT(dentry));

	/* We can't work correctly if our parent isn't valid. */
	pdgen = atomic_read(&UNIONFS_D(parent)->generation);

	/* Free the pointers for our inodes and this dentry. */
	path_put_lowers_all(dentry, false);

	interpose_flag = INTERPOSE_REVAL_NEG;
	if (positive) {
		interpose_flag = INTERPOSE_REVAL;
		iput_lowers_all(dentry->d_inode, true);
	}

	if (realloc_dentry_private_data(dentry) != 0) {
		valid = false;
		goto out;
	}

	result = unionfs_lookup_full(dentry, parent, interpose_flag);
	if (result) {
		if (IS_ERR(result)) {
			valid = false;
			goto out;
		}
		/*
		 * current unionfs_lookup_backend() doesn't return
		 * a valid dentry
		 */
		dput(dentry);
		dentry = result;
	}

	if (unlikely(positive && is_negative_lower(dentry))) {
		/* call make_bad_inode here ? */
		d_drop(dentry);
		valid = false;
		goto out;
	}

	/*
	 * if we got here then we have revalidated our dentry and all lower
	 * ones, so we can return safely.
	 */
	if (!valid)		/* lower dentry revalidation failed */
		goto out;

	/*
	 * If the parent's gen no.  matches the superblock's gen no., then
	 * we can update our denty's gen no.  If they didn't match, then it
	 * was OK to revalidate this dentry with a stale parent, but we'll
	 * purposely not update our dentry's gen no. (so it can be redone);
	 * and, we'll mark our parent dentry as invalid so it'll force it
	 * (and our dentry) to be revalidated.
	 */
	if (pdgen == sbgen)
		atomic_set(&UNIONFS_D(dentry)->generation, sbgen);
	goto out;

validate_lowers:

	/* The revalidation must occur across all branches */
	bstart = dbstart(dentry);
	bend = dbend(dentry);
	BUG_ON(bstart == -1);
	for (bindex = bstart; bindex <= bend; bindex++) {
		lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
		if (!lower_dentry || !lower_dentry->d_op
		    || !lower_dentry->d_op->d_revalidate)
			continue;
		/*
		 * Don't pass nameidata to lower file system, because we
		 * don't want an arbitrary lower file being opened or
		 * returned to us: it may be useless to us because of the
		 * fanout nature of unionfs (cf. file/directory open-file
		 * invariants).  We will open lower files as and when needed
		 * later on.
		 */
		if (!lower_dentry->d_op->d_revalidate(lower_dentry, NULL))
			valid = false;
	}

	if (!dentry->d_inode ||
	    ibstart(dentry->d_inode) < 0 ||
	    ibend(dentry->d_inode) < 0) {
		valid = false;
		goto out;
	}

	if (valid) {
		/*
		 * If we get here, and we copy the meta-data from the lower
		 * inode to our inode, then it is vital that we have already
		 * purged all unionfs-level file data.  We do that in the
		 * caller (__unionfs_d_revalidate) by calling
		 * purge_inode_data.
		 */
		unionfs_copy_attr_all(dentry->d_inode,
				      unionfs_lower_inode(dentry->d_inode));
		fsstack_copy_inode_size(dentry->d_inode,
					unionfs_lower_inode(dentry->d_inode));
	}

out:
	return valid;
}

/*
 * Determine if the lower inode objects have changed from below the unionfs
 * inode.  Return true if changed, false otherwise.
 *
 * We check if the mtime or ctime have changed.  However, the inode times
 * can be changed by anyone without much protection, including
 * asynchronously.  This can sometimes cause unionfs to find that the lower
 * file system doesn't change its inode times quick enough, resulting in a
 * false positive indication (which is harmless, it just makes unionfs do
 * extra work in re-validating the objects).  To minimize the chances of
 * these situations, we still consider such small time changes valid, but we
 * don't print debugging messages unless the time changes are greater than
 * UNIONFS_MIN_CC_TIME (which defaults to 3 seconds, as with NFS's acregmin)
 * because significant changes are more likely due to users manually
 * touching lower files.
 */
bool is_newer_lower(const struct dentry *dentry)
{
	int bindex;
	struct inode *inode;
	struct inode *lower_inode;

	/* ignore if we're called on semi-initialized dentries/inodes */
	if (!dentry || !UNIONFS_D(dentry))
		return false;
	inode = dentry->d_inode;
	if (!inode || !UNIONFS_I(inode)->lower_inodes ||
	    ibstart(inode) < 0 || ibend(inode) < 0)
		return false;

	for (bindex = ibstart(inode); bindex <= ibend(inode); bindex++) {
		lower_inode = unionfs_lower_inode_idx(inode, bindex);
		if (!lower_inode)
			continue;

		/* check if mtime/ctime have changed */
		if (unlikely(timespec_compare(&inode->i_mtime,
					      &lower_inode->i_mtime) < 0)) {
			if ((lower_inode->i_mtime.tv_sec -
			     inode->i_mtime.tv_sec) > UNIONFS_MIN_CC_TIME) {
				pr_info("unionfs: new lower inode mtime "
					"(bindex=%d, name=%s)\n", bindex,
					dentry->d_name.name);
				show_dinode_times(dentry);
			}
			return true;
		}
		if (unlikely(timespec_compare(&inode->i_ctime,
					      &lower_inode->i_ctime) < 0)) {
			if ((lower_inode->i_ctime.tv_sec -
			     inode->i_ctime.tv_sec) > UNIONFS_MIN_CC_TIME) {
				pr_info("unionfs: new lower inode ctime "
					"(bindex=%d, name=%s)\n", bindex,
					dentry->d_name.name);
				show_dinode_times(dentry);
			}
			return true;
		}
	}

	/*
	 * Last check: if this is a positive dentry, but somehow all lower
	 * dentries are negative or unhashed, then this dentry needs to be
	 * revalidated, because someone probably deleted the objects from
	 * the lower branches directly.
	 */
	if (is_negative_lower(dentry))
		return true;

	return false;		/* default: lower is not newer */
}

static int unionfs_d_revalidate(struct dentry *dentry,
				struct nameidata *nd_unused)
{
	bool valid = true;
	int err = 1;		/* 1 means valid for the VFS */
	struct dentry *parent;

	unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
	parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
	unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);

	valid = __unionfs_d_revalidate(dentry, parent, false);
	if (valid) {
		unionfs_postcopyup_setmnt(dentry);
		unionfs_check_dentry(dentry);
	} else {
		d_drop(dentry);
		err = valid;
	}
	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(dentry->d_sb);

	return err;
}

static void unionfs_d_release(struct dentry *dentry)
{
	unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
	if (unlikely(!UNIONFS_D(dentry)))
		goto out;	/* skip if no lower branches */
	/* must lock our branch configuration here */
	unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);

	unionfs_check_dentry(dentry);
	/* this could be a negative dentry, so check first */
	if (dbstart(dentry) < 0) {
		unionfs_unlock_dentry(dentry);
		goto out;	/* due to a (normal) failed lookup */
	}

	/* Release all the lower dentries */
	path_put_lowers_all(dentry, true);

	unionfs_unlock_dentry(dentry);

out:
	free_dentry_private_data(dentry);
	unionfs_read_unlock(dentry->d_sb);
	return;
}

/*
 * Called when we're removing the last reference to our dentry.  So we
 * should drop all lower references too.
 */
static void unionfs_d_iput(struct dentry *dentry, struct inode *inode)
{
	int rc;

	BUG_ON(!dentry);
	unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
	unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);

	if (!UNIONFS_D(dentry) || dbstart(dentry) < 0)
		goto drop_lower_inodes;
	path_put_lowers_all(dentry, false);

drop_lower_inodes:
	rc = atomic_read(&inode->i_count);
	if (rc == 1 && inode->i_nlink == 1 && ibstart(inode) >= 0) {
		/* see Documentation/filesystems/unionfs/issues.txt */
		lockdep_off();
		iput(unionfs_lower_inode(inode));
		lockdep_on();
		unionfs_set_lower_inode(inode, NULL);
		/* XXX: may need to set start/end to -1? */
	}

	iput(inode);

	unionfs_unlock_dentry(dentry);
	unionfs_read_unlock(dentry->d_sb);
}

struct dentry_operations unionfs_dops = {
	.d_revalidate	= unionfs_d_revalidate,
	.d_release	= unionfs_d_release,
	.d_iput		= unionfs_d_iput,
};
OpenPOWER on IntegriCloud