nfs4recover.c 23.3 KB
Newer Older
N
NeilBrown 已提交
1 2
/*
*  Copyright (c) 2004 The Regents of the University of Michigan.
3
*  Copyright (c) 2012 Jeff Layton <jlayton@redhat.com>
N
NeilBrown 已提交
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
*  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.
*
*/

35
#include <linux/file.h>
36
#include <linux/slab.h>
37
#include <linux/namei.h>
N
NeilBrown 已提交
38
#include <linux/crypto.h>
A
Alexey Dobriyan 已提交
39
#include <linux/sched.h>
40
#include <linux/fs.h>
41
#include <linux/module.h>
42 43 44 45
#include <net/net_namespace.h>
#include <linux/sunrpc/rpc_pipe_fs.h>
#include <linux/sunrpc/clnt.h>
#include <linux/nfsd/cld.h>
46 47 48

#include "nfsd.h"
#include "state.h"
49
#include "vfs.h"
50
#include "netns.h"
N
NeilBrown 已提交
51 52 53

#define NFSDDBG_FACILITY                NFSDDBG_PROC

54 55 56 57 58 59 60 61 62 63
/* 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);
};

64
/* Globals */
65
static struct file *rec_file;
66
static char user_recovery_dirname[PATH_MAX] = "/var/lib/nfs/v4recovery";
67
static struct nfsd4_client_tracking_ops *client_tracking_ops;
68

D
David Howells 已提交
69 70
static int
nfs4_save_creds(const struct cred **original_creds)
71
{
D
David Howells 已提交
72 73 74 75 76 77 78 79 80 81 82
	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;
83 84 85
}

static void
D
David Howells 已提交
86
nfs4_reset_creds(const struct cred *original)
87
{
D
David Howells 已提交
88
	revert_creds(original);
89 90
}

N
NeilBrown 已提交
91 92 93 94 95 96 97 98 99 100 101 102 103 104
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';
}

105
__be32
N
NeilBrown 已提交
106 107 108
nfs4_make_rec_clidname(char *dname, struct xdr_netobj *clname)
{
	struct xdr_netobj cksum;
109
	struct hash_desc desc;
J
Jens Axboe 已提交
110
	struct scatterlist sg;
111
	__be32 status = nfserr_jukebox;
N
NeilBrown 已提交
112 113 114

	dprintk("NFSD: nfs4_make_rec_clidname for %.*s\n",
			clname->len, clname->data);
115 116 117 118 119
	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 已提交
120 121 122 123
	cksum.data = kmalloc(cksum.len, GFP_KERNEL);
	if (cksum.data == NULL)
 		goto out;

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

J
Jens Axboe 已提交
126
	if (crypto_hash_digest(&desc, &sg, sg.length, cksum.data))
127
		goto out;
N
NeilBrown 已提交
128 129 130 131 132

	md5_to_hex(dname, cksum.data);

	status = nfs_ok;
out:
133
	kfree(cksum.data);
134 135
	crypto_free_hash(desc.tfm);
out_no_tfm:
N
NeilBrown 已提交
136 137
	return status;
}
138

139 140
static void
nfsd4_create_clid_dir(struct nfs4_client *clp)
141
{
D
David Howells 已提交
142
	const struct cred *original_cred;
143
	char *dname = clp->cl_recdir;
144
	struct dentry *dir, *dentry;
145 146 147 148
	int status;

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

149
	if (test_and_set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
150
		return;
151
	if (!rec_file)
152
		return;
D
David Howells 已提交
153 154
	status = nfs4_save_creds(&original_cred);
	if (status < 0)
155
		return;
156

157 158 159 160
	status = mnt_want_write_file(rec_file);
	if (status)
		return;

161
	dir = rec_file->f_path.dentry;
162
	/* lock the parent */
163
	mutex_lock(&dir->d_inode->i_mutex);
164

165
	dentry = lookup_one_len(dname, dir, HEXDIR_LEN-1);
166 167 168 169
	if (IS_ERR(dentry)) {
		status = PTR_ERR(dentry);
		goto out_unlock;
	}
170
	if (dentry->d_inode)
171 172 173 174 175 176 177 178
		/*
		 * 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.
		 */
179
		goto out_put;
180
	status = vfs_mkdir(dir->d_inode, dentry, S_IRWXU);
181 182 183
out_put:
	dput(dentry);
out_unlock:
184
	mutex_unlock(&dir->d_inode->i_mutex);
185
	if (status == 0)
186
		vfs_fsync(rec_file, 0);
187 188 189 190 191
	else
		printk(KERN_ERR "NFSD: failed to write recovery record"
				" (err %d); please check that %s exists"
				" and is writeable", status,
				user_recovery_dirname);
192
	mnt_drop_write_file(rec_file);
D
David Howells 已提交
193
	nfs4_reset_creds(original_cred);
194 195
}

196 197
typedef int (recdir_func)(struct dentry *, struct dentry *);

198 199
struct name_list {
	char name[HEXDIR_LEN];
200 201 202 203
	struct list_head list;
};

static int
204
nfsd4_build_namelist(void *arg, const char *name, int namlen,
205
		loff_t offset, u64 ino, unsigned int d_type)
206
{
207 208
	struct list_head *names = arg;
	struct name_list *entry;
209

210
	if (namlen != HEXDIR_LEN - 1)
211
		return 0;
212 213
	entry = kmalloc(sizeof(struct name_list), GFP_KERNEL);
	if (entry == NULL)
214
		return -ENOMEM;
215 216 217
	memcpy(entry->name, name, HEXDIR_LEN - 1);
	entry->name[HEXDIR_LEN - 1] = '\0';
	list_add(&entry->list, names);
218 219 220 221
	return 0;
}

static int
222
nfsd4_list_rec_dir(recdir_func *f)
223
{
D
David Howells 已提交
224
	const struct cred *original_cred;
225
	struct dentry *dir = rec_file->f_path.dentry;
226
	LIST_HEAD(names);
227 228
	int status;

D
David Howells 已提交
229 230 231
	status = nfs4_save_creds(&original_cred);
	if (status < 0)
		return status;
232

233 234 235 236 237 238 239
	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 已提交
240
	mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
241
	while (!list_empty(&names)) {
242
		struct name_list *entry;
243
		entry = list_entry(names.next, struct name_list, list);
244 245 246 247 248 249 250 251 252
		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);
253 254 255
		}
		list_del(&entry->list);
		kfree(entry);
256
	}
257
	mutex_unlock(&dir->d_inode->i_mutex);
D
David Howells 已提交
258
	nfs4_reset_creds(original_cred);
259 260 261
	return status;
}

262 263 264
static int
nfsd4_unlink_clid_dir(char *name, int namlen)
{
265
	struct dentry *dir, *dentry;
266 267 268 269
	int status;

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

270 271 272
	dir = rec_file->f_path.dentry;
	mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
	dentry = lookup_one_len(name, dir, namlen);
273 274
	if (IS_ERR(dentry)) {
		status = PTR_ERR(dentry);
275
		goto out_unlock;
276 277 278 279
	}
	status = -ENOENT;
	if (!dentry->d_inode)
		goto out;
280
	status = vfs_rmdir(dir->d_inode, dentry);
281 282
out:
	dput(dentry);
283
out_unlock:
284
	mutex_unlock(&dir->d_inode->i_mutex);
285 286 287
	return status;
}

288
static void
289 290
nfsd4_remove_clid_dir(struct nfs4_client *clp)
{
D
David Howells 已提交
291
	const struct cred *original_cred;
292 293
	int status;

294
	if (!rec_file || !test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
295 296
		return;

297
	status = mnt_want_write_file(rec_file);
298 299
	if (status)
		goto out;
300
	clear_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
D
David Howells 已提交
301 302 303

	status = nfs4_save_creds(&original_cred);
	if (status < 0)
304
		goto out_drop_write;
D
David Howells 已提交
305

306
	status = nfsd4_unlink_clid_dir(clp->cl_recdir, HEXDIR_LEN-1);
D
David Howells 已提交
307
	nfs4_reset_creds(original_cred);
308
	if (status == 0)
309
		vfs_fsync(rec_file, 0);
310
out_drop_write:
A
Al Viro 已提交
311
	mnt_drop_write_file(rec_file);
312
out:
313 314 315 316 317 318 319 320 321 322
	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;

323
	if (nfs4_has_reclaimed_state(child->d_name.name))
324
		return 0;
325

326
	status = vfs_rmdir(parent->d_inode, child);
327 328 329 330
	if (status)
		printk("failed to remove client recovery directory %s\n",
				child->d_name.name);
	/* Keep trying, success or failure: */
331
	return 0;
332 333
}

334 335 336
static void
nfsd4_recdir_purge_old(struct net *net, time_t boot_time)
{
337 338
	int status;

339
	if (!rec_file)
340
		return;
341
	status = mnt_want_write_file(rec_file);
342 343
	if (status)
		goto out;
344
	status = nfsd4_list_rec_dir(purge_old);
345
	if (status == 0)
346
		vfs_fsync(rec_file, 0);
A
Al Viro 已提交
347
	mnt_drop_write_file(rec_file);
348
out:
349 350
	if (status)
		printk("nfsd4: failed to purge old clients from recovery"
351
			" directory %s\n", rec_file->f_path.dentry->d_name.name);
352 353
}

354 355 356 357 358 359 360
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: */
361
		return 0;
362 363
	}
	nfs4_client_to_reclaim(child->d_name.name);
364
	return 0;
365 366
}

367
static int
368 369 370
nfsd4_recdir_load(void) {
	int status;

371 372 373
	if (!rec_file)
		return 0;

374
	status = nfsd4_list_rec_dir(load_recdir);
375 376
	if (status)
		printk("nfsd4: failed loading clients from recovery"
377
			" directory %s\n", rec_file->f_path.dentry->d_name.name);
378 379 380 381 382 383 384
	return status;
}

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

385 386
static int
nfsd4_init_recdir(void)
387
{
D
David Howells 已提交
388 389
	const struct cred *original_cred;
	int status;
390 391

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

394
	BUG_ON(rec_file);
395

D
David Howells 已提交
396 397 398 399 400
	status = nfs4_save_creds(&original_cred);
	if (status < 0) {
		printk("NFSD: Unable to change credentials to find recovery"
		       " directory: error %d\n",
		       status);
401
		return status;
D
David Howells 已提交
402
	}
403

404
	rec_file = filp_open(user_recovery_dirname, O_RDONLY | O_DIRECTORY, 0);
405
	if (IS_ERR(rec_file)) {
406
		printk("NFSD: unable to find recovery directory %s\n",
407
				user_recovery_dirname);
408
		status = PTR_ERR(rec_file);
409 410
		rec_file = NULL;
	}
411

D
David Howells 已提交
412
	nfs4_reset_creds(original_cred);
413
	return status;
414 415
}

416 417 418 419 420
static int
nfsd4_load_reboot_recovery_data(struct net *net)
{
	int status;

421 422 423 424 425 426 427
	/* XXX: The legacy code won't work in a container */
	if (net != &init_net) {
		WARN(1, KERN_ERR "NFSD: attempt to initialize legacy client "
			"tracking in a container!\n");
		return -EINVAL;
	}

428 429 430 431 432 433 434 435 436 437 438
	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
439 440
nfsd4_shutdown_recdir(void)
{
441
	if (!rec_file)
442
		return;
443 444
	fput(rec_file);
	rec_file = NULL;
445
}
446

447 448 449 450 451 452 453
static void
nfsd4_legacy_tracking_exit(struct net *net)
{
	nfs4_release_reclaim();
	nfsd4_shutdown_recdir();
}

454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
/*
 * 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;
}
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

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,
};

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 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
/* Globals */
#define NFSD_PIPE_DIR		"nfsd"
#define NFSD_CLD_PIPE		"cld"

/* per-net-ns structure for holding cld upcall info */
struct cld_net {
	struct rpc_pipe		*cn_pipe;
	spinlock_t		 cn_lock;
	struct list_head	 cn_list;
	unsigned int		 cn_xid;
};

struct cld_upcall {
	struct list_head	 cu_list;
	struct cld_net		*cu_net;
	struct task_struct	*cu_task;
	struct cld_msg		 cu_msg;
};

static int
__cld_pipe_upcall(struct rpc_pipe *pipe, struct cld_msg *cmsg)
{
	int ret;
	struct rpc_pipe_msg msg;

	memset(&msg, 0, sizeof(msg));
	msg.data = cmsg;
	msg.len = sizeof(*cmsg);

	/*
	 * Set task state before we queue the upcall. That prevents
	 * wake_up_process in the downcall from racing with schedule.
	 */
	set_current_state(TASK_UNINTERRUPTIBLE);
	ret = rpc_queue_upcall(pipe, &msg);
	if (ret < 0) {
		set_current_state(TASK_RUNNING);
		goto out;
	}

	schedule();
	set_current_state(TASK_RUNNING);

	if (msg.errno < 0)
		ret = msg.errno;
out:
	return ret;
}

static int
cld_pipe_upcall(struct rpc_pipe *pipe, struct cld_msg *cmsg)
{
	int ret;

	/*
	 * -EAGAIN occurs when pipe is closed and reopened while there are
	 *  upcalls queued.
	 */
	do {
		ret = __cld_pipe_upcall(pipe, cmsg);
	} while (ret == -EAGAIN);

	return ret;
}

static ssize_t
cld_pipe_downcall(struct file *filp, const char __user *src, size_t mlen)
{
	struct cld_upcall *tmp, *cup;
575
	struct cld_msg __user *cmsg = (struct cld_msg __user *)src;
576 577 578 579 580 581
	uint32_t xid;
	struct nfsd_net *nn = net_generic(filp->f_dentry->d_sb->s_fs_info,
						nfsd_net_id);
	struct cld_net *cn = nn->cld_net;

	if (mlen != sizeof(*cmsg)) {
582
		dprintk("%s: got %zu bytes, expected %zu\n", __func__, mlen,
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606
			sizeof(*cmsg));
		return -EINVAL;
	}

	/* copy just the xid so we can try to find that */
	if (copy_from_user(&xid, &cmsg->cm_xid, sizeof(xid)) != 0) {
		dprintk("%s: error when copying xid from userspace", __func__);
		return -EFAULT;
	}

	/* walk the list and find corresponding xid */
	cup = NULL;
	spin_lock(&cn->cn_lock);
	list_for_each_entry(tmp, &cn->cn_list, cu_list) {
		if (get_unaligned(&tmp->cu_msg.cm_xid) == xid) {
			cup = tmp;
			list_del_init(&cup->cu_list);
			break;
		}
	}
	spin_unlock(&cn->cn_lock);

	/* couldn't find upcall? */
	if (!cup) {
607
		dprintk("%s: couldn't find upcall -- xid=%u\n", __func__, xid);
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
		return -EINVAL;
	}

	if (copy_from_user(&cup->cu_msg, src, mlen) != 0)
		return -EFAULT;

	wake_up_process(cup->cu_task);
	return mlen;
}

static void
cld_pipe_destroy_msg(struct rpc_pipe_msg *msg)
{
	struct cld_msg *cmsg = msg->data;
	struct cld_upcall *cup = container_of(cmsg, struct cld_upcall,
						 cu_msg);

	/* errno >= 0 means we got a downcall */
	if (msg->errno >= 0)
		return;

	wake_up_process(cup->cu_task);
}

static const struct rpc_pipe_ops cld_upcall_ops = {
	.upcall		= rpc_pipe_generic_upcall,
	.downcall	= cld_pipe_downcall,
	.destroy_msg	= cld_pipe_destroy_msg,
};

static struct dentry *
nfsd4_cld_register_sb(struct super_block *sb, struct rpc_pipe *pipe)
{
	struct dentry *dir, *dentry;

	dir = rpc_d_lookup_sb(sb, NFSD_PIPE_DIR);
	if (dir == NULL)
		return ERR_PTR(-ENOENT);
	dentry = rpc_mkpipe_dentry(dir, NFSD_CLD_PIPE, NULL, pipe);
	dput(dir);
	return dentry;
}

static void
nfsd4_cld_unregister_sb(struct rpc_pipe *pipe)
{
	if (pipe->dentry)
		rpc_unlink(pipe->dentry);
}

static struct dentry *
nfsd4_cld_register_net(struct net *net, struct rpc_pipe *pipe)
{
	struct super_block *sb;
	struct dentry *dentry;

	sb = rpc_get_sb_net(net);
	if (!sb)
		return NULL;
	dentry = nfsd4_cld_register_sb(sb, pipe);
	rpc_put_sb_net(net);
	return dentry;
}

static void
nfsd4_cld_unregister_net(struct net *net, struct rpc_pipe *pipe)
{
	struct super_block *sb;

	sb = rpc_get_sb_net(net);
	if (sb) {
		nfsd4_cld_unregister_sb(pipe);
		rpc_put_sb_net(net);
	}
}

/* Initialize rpc_pipefs pipe for communication with client tracking daemon */
static int
nfsd4_init_cld_pipe(struct net *net)
{
	int ret;
	struct dentry *dentry;
	struct nfsd_net *nn = net_generic(net, nfsd_net_id);
	struct cld_net *cn;

	if (nn->cld_net)
		return 0;

	cn = kzalloc(sizeof(*cn), GFP_KERNEL);
	if (!cn) {
		ret = -ENOMEM;
		goto err;
	}

	cn->cn_pipe = rpc_mkpipe_data(&cld_upcall_ops, RPC_PIPE_WAIT_FOR_OPEN);
	if (IS_ERR(cn->cn_pipe)) {
		ret = PTR_ERR(cn->cn_pipe);
		goto err;
	}
	spin_lock_init(&cn->cn_lock);
	INIT_LIST_HEAD(&cn->cn_list);

	dentry = nfsd4_cld_register_net(net, cn->cn_pipe);
	if (IS_ERR(dentry)) {
		ret = PTR_ERR(dentry);
		goto err_destroy_data;
	}

	cn->cn_pipe->dentry = dentry;
	nn->cld_net = cn;
	return 0;

err_destroy_data:
	rpc_destroy_pipe_data(cn->cn_pipe);
err:
	kfree(cn);
	printk(KERN_ERR "NFSD: unable to create nfsdcld upcall pipe (%d)\n",
			ret);
	return ret;
}

static void
nfsd4_remove_cld_pipe(struct net *net)
{
	struct nfsd_net *nn = net_generic(net, nfsd_net_id);
	struct cld_net *cn = nn->cld_net;

	nfsd4_cld_unregister_net(net, cn->cn_pipe);
	rpc_destroy_pipe_data(cn->cn_pipe);
	kfree(nn->cld_net);
	nn->cld_net = NULL;
}

static struct cld_upcall *
alloc_cld_upcall(struct cld_net *cn)
{
	struct cld_upcall *new, *tmp;

	new = kzalloc(sizeof(*new), GFP_KERNEL);
	if (!new)
		return new;

	/* FIXME: hard cap on number in flight? */
restart_search:
	spin_lock(&cn->cn_lock);
	list_for_each_entry(tmp, &cn->cn_list, cu_list) {
		if (tmp->cu_msg.cm_xid == cn->cn_xid) {
			cn->cn_xid++;
			spin_unlock(&cn->cn_lock);
			goto restart_search;
		}
	}
	new->cu_task = current;
	new->cu_msg.cm_vers = CLD_UPCALL_VERSION;
	put_unaligned(cn->cn_xid++, &new->cu_msg.cm_xid);
	new->cu_net = cn;
	list_add(&new->cu_list, &cn->cn_list);
	spin_unlock(&cn->cn_lock);

	dprintk("%s: allocated xid %u\n", __func__, new->cu_msg.cm_xid);

	return new;
}

static void
free_cld_upcall(struct cld_upcall *victim)
{
	struct cld_net *cn = victim->cu_net;

	spin_lock(&cn->cn_lock);
	list_del(&victim->cu_list);
	spin_unlock(&cn->cn_lock);
	kfree(victim);
}

/* Ask daemon to create a new record */
static void
nfsd4_cld_create(struct nfs4_client *clp)
{
	int ret;
	struct cld_upcall *cup;
	/* FIXME: determine net from clp */
	struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);
	struct cld_net *cn = nn->cld_net;

	/* Don't upcall if it's already stored */
	if (test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
		return;

	cup = alloc_cld_upcall(cn);
	if (!cup) {
		ret = -ENOMEM;
		goto out_err;
	}

	cup->cu_msg.cm_cmd = Cld_Create;
	cup->cu_msg.cm_u.cm_name.cn_len = clp->cl_name.len;
	memcpy(cup->cu_msg.cm_u.cm_name.cn_id, clp->cl_name.data,
			clp->cl_name.len);

	ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
	if (!ret) {
		ret = cup->cu_msg.cm_status;
		set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
	}

	free_cld_upcall(cup);
out_err:
	if (ret)
		printk(KERN_ERR "NFSD: Unable to create client "
				"record on stable storage: %d\n", ret);
}

/* Ask daemon to create a new record */
static void
nfsd4_cld_remove(struct nfs4_client *clp)
{
	int ret;
	struct cld_upcall *cup;
	/* FIXME: determine net from clp */
	struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);
	struct cld_net *cn = nn->cld_net;

	/* Don't upcall if it's already removed */
	if (!test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
		return;

	cup = alloc_cld_upcall(cn);
	if (!cup) {
		ret = -ENOMEM;
		goto out_err;
	}

	cup->cu_msg.cm_cmd = Cld_Remove;
	cup->cu_msg.cm_u.cm_name.cn_len = clp->cl_name.len;
	memcpy(cup->cu_msg.cm_u.cm_name.cn_id, clp->cl_name.data,
			clp->cl_name.len);

	ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
	if (!ret) {
		ret = cup->cu_msg.cm_status;
		clear_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
	}

	free_cld_upcall(cup);
out_err:
	if (ret)
		printk(KERN_ERR "NFSD: Unable to remove client "
				"record from stable storage: %d\n", ret);
}

/* Check for presence of a record, and update its timestamp */
static int
nfsd4_cld_check(struct nfs4_client *clp)
{
	int ret;
	struct cld_upcall *cup;
	/* FIXME: determine net from clp */
	struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);
	struct cld_net *cn = nn->cld_net;

	/* Don't upcall if one was already stored during this grace pd */
	if (test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
		return 0;

	cup = alloc_cld_upcall(cn);
	if (!cup) {
		printk(KERN_ERR "NFSD: Unable to check client record on "
				"stable storage: %d\n", -ENOMEM);
		return -ENOMEM;
	}

	cup->cu_msg.cm_cmd = Cld_Check;
	cup->cu_msg.cm_u.cm_name.cn_len = clp->cl_name.len;
	memcpy(cup->cu_msg.cm_u.cm_name.cn_id, clp->cl_name.data,
			clp->cl_name.len);

	ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
	if (!ret) {
		ret = cup->cu_msg.cm_status;
		set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
	}

	free_cld_upcall(cup);
	return ret;
}

static void
nfsd4_cld_grace_done(struct net *net, time_t boot_time)
{
	int ret;
	struct cld_upcall *cup;
	struct nfsd_net *nn = net_generic(net, nfsd_net_id);
	struct cld_net *cn = nn->cld_net;

	cup = alloc_cld_upcall(cn);
	if (!cup) {
		ret = -ENOMEM;
		goto out_err;
	}

	cup->cu_msg.cm_cmd = Cld_GraceDone;
	cup->cu_msg.cm_u.cm_gracetime = (int64_t)boot_time;
	ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
	if (!ret)
		ret = cup->cu_msg.cm_status;

	free_cld_upcall(cup);
out_err:
	if (ret)
		printk(KERN_ERR "NFSD: Unable to end grace period: %d\n", ret);
}

static struct nfsd4_client_tracking_ops nfsd4_cld_tracking_ops = {
	.init		= nfsd4_init_cld_pipe,
	.exit		= nfsd4_remove_cld_pipe,
	.create		= nfsd4_cld_create,
	.remove		= nfsd4_cld_remove,
	.check		= nfsd4_cld_check,
	.grace_done	= nfsd4_cld_grace_done,
};

930 931 932 933
int
nfsd4_client_tracking_init(struct net *net)
{
	int status;
934
	struct path path;
935

936 937 938 939 940 941 942 943 944 945
	if (!client_tracking_ops) {
		client_tracking_ops = &nfsd4_cld_tracking_ops;
		status = kern_path(nfs4_recoverydir(), LOOKUP_FOLLOW, &path);
		if (!status) {
			if (S_ISDIR(path.dentry->d_inode->i_mode))
				client_tracking_ops =
						&nfsd4_legacy_tracking_ops;
			path_put(&path);
		}
	}
946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993

	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);
}
994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033

static int
rpc_pipefs_event(struct notifier_block *nb, unsigned long event, void *ptr)
{
	struct super_block *sb = ptr;
	struct net *net = sb->s_fs_info;
	struct nfsd_net *nn = net_generic(net, nfsd_net_id);
	struct cld_net *cn = nn->cld_net;
	struct dentry *dentry;
	int ret = 0;

	if (!try_module_get(THIS_MODULE))
		return 0;

	if (!cn) {
		module_put(THIS_MODULE);
		return 0;
	}

	switch (event) {
	case RPC_PIPEFS_MOUNT:
		dentry = nfsd4_cld_register_sb(sb, cn->cn_pipe);
		if (IS_ERR(dentry)) {
			ret = PTR_ERR(dentry);
			break;
		}
		cn->cn_pipe->dentry = dentry;
		break;
	case RPC_PIPEFS_UMOUNT:
		if (cn->cn_pipe->dentry)
			nfsd4_cld_unregister_sb(cn->cn_pipe);
		break;
	default:
		ret = -ENOTSUPP;
		break;
	}
	module_put(THIS_MODULE);
	return ret;
}

J
J. Bruce Fields 已提交
1034
static struct notifier_block nfsd4_cld_block = {
1035 1036
	.notifier_call = rpc_pipefs_event,
};
1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048

int
register_cld_notifier(void)
{
	return rpc_pipefs_notifier_register(&nfsd4_cld_block);
}

void
unregister_cld_notifier(void)
{
	rpc_pipefs_notifier_unregister(&nfsd4_cld_block);
}