nfs4recover.c 12.3 KB
Newer Older
N
NeilBrown 已提交
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
/*
*  Copyright (c) 2004 The Regents of the University of Michigan.
*  All rights reserved.
*
*  Andy Adamson <andros@citi.umich.edu>
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*  1. Redistributions of source code must retain the above copyright
*     notice, this list of conditions and the following disclaimer.
*  2. Redistributions in binary form must reproduce the above copyright
*     notice, this list of conditions and the following disclaimer in the
*     documentation and/or other materials provided with the distribution.
*  3. Neither the name of the University nor the names of its
*     contributors may be used to endorse or promote products derived
*     from this software without specific prior written permission.
*
*  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
*  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
*  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*  DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
*  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
*  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
*  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
*  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
*  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

34
#include <linux/file.h>
35
#include <linux/slab.h>
36
#include <linux/namei.h>
N
NeilBrown 已提交
37
#include <linux/crypto.h>
A
Alexey Dobriyan 已提交
38
#include <linux/sched.h>
39 40 41

#include "nfsd.h"
#include "state.h"
42
#include "vfs.h"
N
NeilBrown 已提交
43 44 45

#define NFSDDBG_FACILITY                NFSDDBG_PROC

46 47 48 49 50 51 52 53 54 55
/* Declarations */
struct nfsd4_client_tracking_ops {
	int (*init)(struct net *);
	void (*exit)(struct net *);
	void (*create)(struct nfs4_client *);
	void (*remove)(struct nfs4_client *);
	int (*check)(struct nfs4_client *);
	void (*grace_done)(struct net *, time_t);
};

56
/* Globals */
57
static struct file *rec_file;
58
static char user_recovery_dirname[PATH_MAX] = "/var/lib/nfs/v4recovery";
59
static struct nfsd4_client_tracking_ops *client_tracking_ops;
60

D
David Howells 已提交
61 62
static int
nfs4_save_creds(const struct cred **original_creds)
63
{
D
David Howells 已提交
64 65 66 67 68 69 70 71 72 73 74
	struct cred *new;

	new = prepare_creds();
	if (!new)
		return -ENOMEM;

	new->fsuid = 0;
	new->fsgid = 0;
	*original_creds = override_creds(new);
	put_cred(new);
	return 0;
75 76 77
}

static void
D
David Howells 已提交
78
nfs4_reset_creds(const struct cred *original)
79
{
D
David Howells 已提交
80
	revert_creds(original);
81 82
}

N
NeilBrown 已提交
83 84 85 86 87 88 89 90 91 92 93 94 95 96
static void
md5_to_hex(char *out, char *md5)
{
	int i;

	for (i=0; i<16; i++) {
		unsigned char c = md5[i];

		*out++ = '0' + ((c&0xf0)>>4) + (c>=0xa0)*('a'-'9'-1);
		*out++ = '0' + (c&0x0f) + ((c&0x0f)>=0x0a)*('a'-'9'-1);
	}
	*out = '\0';
}

97
__be32
N
NeilBrown 已提交
98 99 100
nfs4_make_rec_clidname(char *dname, struct xdr_netobj *clname)
{
	struct xdr_netobj cksum;
101
	struct hash_desc desc;
J
Jens Axboe 已提交
102
	struct scatterlist sg;
103
	__be32 status = nfserr_jukebox;
N
NeilBrown 已提交
104 105 106

	dprintk("NFSD: nfs4_make_rec_clidname for %.*s\n",
			clname->len, clname->data);
107 108 109 110 111
	desc.flags = CRYPTO_TFM_REQ_MAY_SLEEP;
	desc.tfm = crypto_alloc_hash("md5", 0, CRYPTO_ALG_ASYNC);
	if (IS_ERR(desc.tfm))
		goto out_no_tfm;
	cksum.len = crypto_hash_digestsize(desc.tfm);
N
NeilBrown 已提交
112 113 114 115
	cksum.data = kmalloc(cksum.len, GFP_KERNEL);
	if (cksum.data == NULL)
 		goto out;

J
Jens Axboe 已提交
116
	sg_init_one(&sg, clname->data, clname->len);
N
NeilBrown 已提交
117

J
Jens Axboe 已提交
118
	if (crypto_hash_digest(&desc, &sg, sg.length, cksum.data))
119
		goto out;
N
NeilBrown 已提交
120 121 122 123 124

	md5_to_hex(dname, cksum.data);

	status = nfs_ok;
out:
125
	kfree(cksum.data);
126 127
	crypto_free_hash(desc.tfm);
out_no_tfm:
N
NeilBrown 已提交
128 129
	return status;
}
130

131 132
static void
nfsd4_create_clid_dir(struct nfs4_client *clp)
133
{
D
David Howells 已提交
134
	const struct cred *original_cred;
135
	char *dname = clp->cl_recdir;
136
	struct dentry *dir, *dentry;
137 138 139 140
	int status;

	dprintk("NFSD: nfsd4_create_clid_dir for \"%s\"\n", dname);

141
	if (test_and_set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
142
		return;
143
	if (!rec_file)
144
		return;
D
David Howells 已提交
145 146
	status = nfs4_save_creds(&original_cred);
	if (status < 0)
147
		return;
148

149
	dir = rec_file->f_path.dentry;
150
	/* lock the parent */
151
	mutex_lock(&dir->d_inode->i_mutex);
152

153
	dentry = lookup_one_len(dname, dir, HEXDIR_LEN-1);
154 155 156 157
	if (IS_ERR(dentry)) {
		status = PTR_ERR(dentry);
		goto out_unlock;
	}
158
	if (dentry->d_inode)
159 160 161 162 163 164 165 166
		/*
		 * In the 4.1 case, where we're called from
		 * reclaim_complete(), records from the previous reboot
		 * may still be left, so this is OK.
		 *
		 * In the 4.0 case, we should never get here; but we may
		 * as well be forgiving and just succeed silently.
		 */
167
		goto out_put;
168
	status = mnt_want_write_file(rec_file);
169 170
	if (status)
		goto out_put;
171
	status = vfs_mkdir(dir->d_inode, dentry, S_IRWXU);
A
Al Viro 已提交
172
	mnt_drop_write_file(rec_file);
173 174 175
out_put:
	dput(dentry);
out_unlock:
176
	mutex_unlock(&dir->d_inode->i_mutex);
177
	if (status == 0)
178
		vfs_fsync(rec_file, 0);
179 180 181 182 183
	else
		printk(KERN_ERR "NFSD: failed to write recovery record"
				" (err %d); please check that %s exists"
				" and is writeable", status,
				user_recovery_dirname);
D
David Howells 已提交
184
	nfs4_reset_creds(original_cred);
185 186
}

187 188
typedef int (recdir_func)(struct dentry *, struct dentry *);

189 190
struct name_list {
	char name[HEXDIR_LEN];
191 192 193 194
	struct list_head list;
};

static int
195
nfsd4_build_namelist(void *arg, const char *name, int namlen,
196
		loff_t offset, u64 ino, unsigned int d_type)
197
{
198 199
	struct list_head *names = arg;
	struct name_list *entry;
200

201
	if (namlen != HEXDIR_LEN - 1)
202
		return 0;
203 204
	entry = kmalloc(sizeof(struct name_list), GFP_KERNEL);
	if (entry == NULL)
205
		return -ENOMEM;
206 207 208
	memcpy(entry->name, name, HEXDIR_LEN - 1);
	entry->name[HEXDIR_LEN - 1] = '\0';
	list_add(&entry->list, names);
209 210 211 212
	return 0;
}

static int
213
nfsd4_list_rec_dir(recdir_func *f)
214
{
D
David Howells 已提交
215
	const struct cred *original_cred;
216
	struct dentry *dir = rec_file->f_path.dentry;
217
	LIST_HEAD(names);
218 219
	int status;

D
David Howells 已提交
220 221 222
	status = nfs4_save_creds(&original_cred);
	if (status < 0)
		return status;
223

224 225 226 227 228 229 230
	status = vfs_llseek(rec_file, 0, SEEK_SET);
	if (status < 0) {
		nfs4_reset_creds(original_cred);
		return status;
	}

	status = vfs_readdir(rec_file, nfsd4_build_namelist, &names);
J
J. Bruce Fields 已提交
231
	mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
232
	while (!list_empty(&names)) {
233
		struct name_list *entry;
234
		entry = list_entry(names.next, struct name_list, list);
235 236 237 238 239 240 241 242 243
		if (!status) {
			struct dentry *dentry;
			dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1);
			if (IS_ERR(dentry)) {
				status = PTR_ERR(dentry);
				break;
			}
			status = f(dir, dentry);
			dput(dentry);
244 245 246
		}
		list_del(&entry->list);
		kfree(entry);
247
	}
248
	mutex_unlock(&dir->d_inode->i_mutex);
D
David Howells 已提交
249
	nfs4_reset_creds(original_cred);
250 251 252
	return status;
}

253 254 255
static int
nfsd4_unlink_clid_dir(char *name, int namlen)
{
256
	struct dentry *dir, *dentry;
257 258 259 260
	int status;

	dprintk("NFSD: nfsd4_unlink_clid_dir. name %.*s\n", namlen, name);

261 262 263
	dir = rec_file->f_path.dentry;
	mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
	dentry = lookup_one_len(name, dir, namlen);
264 265
	if (IS_ERR(dentry)) {
		status = PTR_ERR(dentry);
266
		goto out_unlock;
267 268 269 270
	}
	status = -ENOENT;
	if (!dentry->d_inode)
		goto out;
271
	status = vfs_rmdir(dir->d_inode, dentry);
272 273
out:
	dput(dentry);
274
out_unlock:
275
	mutex_unlock(&dir->d_inode->i_mutex);
276 277 278
	return status;
}

279
static void
280 281
nfsd4_remove_clid_dir(struct nfs4_client *clp)
{
D
David Howells 已提交
282
	const struct cred *original_cred;
283 284
	int status;

285
	if (!rec_file || !test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
286 287
		return;

288
	status = mnt_want_write_file(rec_file);
289 290
	if (status)
		goto out;
291
	clear_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
D
David Howells 已提交
292 293 294 295 296

	status = nfs4_save_creds(&original_cred);
	if (status < 0)
		goto out;

297
	status = nfsd4_unlink_clid_dir(clp->cl_recdir, HEXDIR_LEN-1);
D
David Howells 已提交
298
	nfs4_reset_creds(original_cred);
299
	if (status == 0)
300
		vfs_fsync(rec_file, 0);
A
Al Viro 已提交
301
	mnt_drop_write_file(rec_file);
302
out:
303 304 305 306 307 308 309 310 311 312
	if (status)
		printk("NFSD: Failed to remove expired client state directory"
				" %.*s\n", HEXDIR_LEN, clp->cl_recdir);
}

static int
purge_old(struct dentry *parent, struct dentry *child)
{
	int status;

313
	if (nfs4_has_reclaimed_state(child->d_name.name, false))
314
		return 0;
315

316
	status = vfs_rmdir(parent->d_inode, child);
317 318 319 320
	if (status)
		printk("failed to remove client recovery directory %s\n",
				child->d_name.name);
	/* Keep trying, success or failure: */
321
	return 0;
322 323
}

324 325 326
static void
nfsd4_recdir_purge_old(struct net *net, time_t boot_time)
{
327 328
	int status;

329
	if (!rec_file)
330
		return;
331
	status = mnt_want_write_file(rec_file);
332 333
	if (status)
		goto out;
334
	status = nfsd4_list_rec_dir(purge_old);
335
	if (status == 0)
336
		vfs_fsync(rec_file, 0);
A
Al Viro 已提交
337
	mnt_drop_write_file(rec_file);
338
out:
339 340
	if (status)
		printk("nfsd4: failed to purge old clients from recovery"
341
			" directory %s\n", rec_file->f_path.dentry->d_name.name);
342 343
}

344 345 346 347 348 349 350
static int
load_recdir(struct dentry *parent, struct dentry *child)
{
	if (child->d_name.len != HEXDIR_LEN - 1) {
		printk("nfsd4: illegal name %s in recovery directory\n",
				child->d_name.name);
		/* Keep trying; maybe the others are OK: */
351
		return 0;
352 353
	}
	nfs4_client_to_reclaim(child->d_name.name);
354
	return 0;
355 356
}

357
static int
358 359 360
nfsd4_recdir_load(void) {
	int status;

361 362 363
	if (!rec_file)
		return 0;

364
	status = nfsd4_list_rec_dir(load_recdir);
365 366
	if (status)
		printk("nfsd4: failed loading clients from recovery"
367
			" directory %s\n", rec_file->f_path.dentry->d_name.name);
368 369 370 371 372 373 374
	return status;
}

/*
 * Hold reference to the recovery directory.
 */

375 376
static int
nfsd4_init_recdir(void)
377
{
D
David Howells 已提交
378 379
	const struct cred *original_cred;
	int status;
380 381

	printk("NFSD: Using %s as the NFSv4 state recovery directory\n",
382
			user_recovery_dirname);
383

384
	BUG_ON(rec_file);
385

D
David Howells 已提交
386 387 388 389 390
	status = nfs4_save_creds(&original_cred);
	if (status < 0) {
		printk("NFSD: Unable to change credentials to find recovery"
		       " directory: error %d\n",
		       status);
391
		return status;
D
David Howells 已提交
392
	}
393

394
	rec_file = filp_open(user_recovery_dirname, O_RDONLY | O_DIRECTORY, 0);
395
	if (IS_ERR(rec_file)) {
396
		printk("NFSD: unable to find recovery directory %s\n",
397
				user_recovery_dirname);
398
		status = PTR_ERR(rec_file);
399 400
		rec_file = NULL;
	}
401

D
David Howells 已提交
402
	nfs4_reset_creds(original_cred);
403
	return status;
404 405
}

406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
static int
nfsd4_load_reboot_recovery_data(struct net *net)
{
	int status;

	nfs4_lock_state();
	status = nfsd4_init_recdir();
	if (!status)
		status = nfsd4_recdir_load();
	nfs4_unlock_state();
	if (status)
		printk(KERN_ERR "NFSD: Failure reading reboot recovery data\n");
	return status;
}

static void
422 423
nfsd4_shutdown_recdir(void)
{
424
	if (!rec_file)
425
		return;
426 427
	fput(rec_file);
	rec_file = NULL;
428
}
429

430 431 432 433 434 435 436
static void
nfsd4_legacy_tracking_exit(struct net *net)
{
	nfs4_release_reclaim();
	nfsd4_shutdown_recdir();
}

437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
/*
 * Change the NFSv4 recovery directory to recdir.
 */
int
nfs4_reset_recoverydir(char *recdir)
{
	int status;
	struct path path;

	status = kern_path(recdir, LOOKUP_FOLLOW, &path);
	if (status)
		return status;
	status = -ENOTDIR;
	if (S_ISDIR(path.dentry->d_inode->i_mode)) {
		strcpy(user_recovery_dirname, recdir);
		status = 0;
	}
	path_put(&path);
	return status;
}

char *
nfs4_recoverydir(void)
{
	return user_recovery_dirname;
}
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542

static int
nfsd4_check_legacy_client(struct nfs4_client *clp)
{
	/* did we already find that this client is stable? */
	if (test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
		return 0;

	/* look for it in the reclaim hashtable otherwise */
	if (nfsd4_find_reclaim_client(clp)) {
		set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
		return 0;
	}

	return -ENOENT;
}

static struct nfsd4_client_tracking_ops nfsd4_legacy_tracking_ops = {
	.init		= nfsd4_load_reboot_recovery_data,
	.exit		= nfsd4_legacy_tracking_exit,
	.create		= nfsd4_create_clid_dir,
	.remove		= nfsd4_remove_clid_dir,
	.check		= nfsd4_check_legacy_client,
	.grace_done	= nfsd4_recdir_purge_old,
};

int
nfsd4_client_tracking_init(struct net *net)
{
	int status;

	client_tracking_ops = &nfsd4_legacy_tracking_ops;

	status = client_tracking_ops->init(net);
	if (status) {
		printk(KERN_WARNING "NFSD: Unable to initialize client "
				    "recovery tracking! (%d)\n", status);
		client_tracking_ops = NULL;
	}
	return status;
}

void
nfsd4_client_tracking_exit(struct net *net)
{
	if (client_tracking_ops) {
		client_tracking_ops->exit(net);
		client_tracking_ops = NULL;
	}
}

void
nfsd4_client_record_create(struct nfs4_client *clp)
{
	if (client_tracking_ops)
		client_tracking_ops->create(clp);
}

void
nfsd4_client_record_remove(struct nfs4_client *clp)
{
	if (client_tracking_ops)
		client_tracking_ops->remove(clp);
}

int
nfsd4_client_record_check(struct nfs4_client *clp)
{
	if (client_tracking_ops)
		return client_tracking_ops->check(clp);

	return -EOPNOTSUPP;
}

void
nfsd4_record_grace_done(struct net *net, time_t boot_time)
{
	if (client_tracking_ops)
		client_tracking_ops->grace_done(net, boot_time);
}