nfs4recover.c 23.2 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
	dir = rec_file->f_path.dentry;
158
	/* lock the parent */
159
	mutex_lock(&dir->d_inode->i_mutex);
160

161
	dentry = lookup_one_len(dname, dir, HEXDIR_LEN-1);
162 163 164 165
	if (IS_ERR(dentry)) {
		status = PTR_ERR(dentry);
		goto out_unlock;
	}
166
	if (dentry->d_inode)
167 168 169 170 171 172 173 174
		/*
		 * 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.
		 */
175
		goto out_put;
176
	status = mnt_want_write_file(rec_file);
177 178
	if (status)
		goto out_put;
179
	status = vfs_mkdir(dir->d_inode, dentry, S_IRWXU);
A
Al Viro 已提交
180
	mnt_drop_write_file(rec_file);
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);
D
David Howells 已提交
192
	nfs4_reset_creds(original_cred);
193 194
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

321
	if (nfs4_has_reclaimed_state(child->d_name.name, false))
322
		return 0;
323

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

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

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

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

365
static int
366 367 368
nfsd4_recdir_load(void) {
	int status;

369 370 371
	if (!rec_file)
		return 0;

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

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

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

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

392
	BUG_ON(rec_file);
393

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

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

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

414 415 416 417 418
static int
nfsd4_load_reboot_recovery_data(struct net *net)
{
	int status;

419 420 421 422 423 424 425
	/* 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;
	}

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

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

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

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

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 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
/* 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;
573
	struct cld_msg __user *cmsg = (struct cld_msg __user *)src;
574 575 576 577 578 579
	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)) {
580
		dprintk("%s: got %zu bytes, expected %zu\n", __func__, mlen,
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
			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) {
605
		dprintk("%s: couldn't find upcall -- xid=%u\n", __func__, xid);
606 607 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
		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,
};

928 929 930 931
int
nfsd4_client_tracking_init(struct net *net)
{
	int status;
932
	struct path path;
933

934 935 936 937 938 939 940 941 942 943
	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);
		}
	}
944 945 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

	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);
}
992 993 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

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 已提交
1032
static struct notifier_block nfsd4_cld_block = {
1033 1034
	.notifier_call = rpc_pipefs_event,
};
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046

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