diff options
Diffstat (limited to 'fs/nfsd/nfs4recover.c')
| -rw-r--r-- | fs/nfsd/nfs4recover.c | 561 | 
1 files changed, 461 insertions, 100 deletions
diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c index 43295d45cc2..ba6fdd4a045 100644 --- a/fs/nfsd/nfs4recover.c +++ b/fs/nfsd/nfs4recover.c @@ -58,13 +58,11 @@ struct nfsd4_client_tracking_ops {  	void (*create)(struct nfs4_client *);  	void (*remove)(struct nfs4_client *);  	int (*check)(struct nfs4_client *); -	void (*grace_done)(struct net *, time_t); +	void (*grace_done)(struct nfsd_net *, time_t);  };  /* Globals */ -static struct file *rec_file;  static char user_recovery_dirname[PATH_MAX] = "/var/lib/nfs/v4recovery"; -static struct nfsd4_client_tracking_ops *client_tracking_ops;  static int  nfs4_save_creds(const struct cred **original_creds) @@ -102,33 +100,39 @@ md5_to_hex(char *out, char *md5)  	*out = '\0';  } -__be32 -nfs4_make_rec_clidname(char *dname, struct xdr_netobj *clname) +static int +nfs4_make_rec_clidname(char *dname, const struct xdr_netobj *clname)  {  	struct xdr_netobj cksum;  	struct hash_desc desc;  	struct scatterlist sg; -	__be32 status = nfserr_jukebox; +	int status;  	dprintk("NFSD: nfs4_make_rec_clidname for %.*s\n",  			clname->len, clname->data);  	desc.flags = CRYPTO_TFM_REQ_MAY_SLEEP;  	desc.tfm = crypto_alloc_hash("md5", 0, CRYPTO_ALG_ASYNC); -	if (IS_ERR(desc.tfm)) +	if (IS_ERR(desc.tfm)) { +		status = PTR_ERR(desc.tfm);  		goto out_no_tfm; +	} +  	cksum.len = crypto_hash_digestsize(desc.tfm);  	cksum.data = kmalloc(cksum.len, GFP_KERNEL); -	if (cksum.data == NULL) +	if (cksum.data == NULL) { +		status = -ENOMEM;   		goto out; +	}  	sg_init_one(&sg, clname->data, clname->len); -	if (crypto_hash_digest(&desc, &sg, sg.length, cksum.data)) +	status = crypto_hash_digest(&desc, &sg, sg.length, cksum.data); +	if (status)  		goto out;  	md5_to_hex(dname, cksum.data); -	status = nfs_ok; +	status = 0;  out:  	kfree(cksum.data);  	crypto_free_hash(desc.tfm); @@ -136,29 +140,61 @@ out_no_tfm:  	return status;  } +/* + * If we had an error generating the recdir name for the legacy tracker + * then warn the admin. If the error doesn't appear to be transient, + * then disable recovery tracking. + */ +static void +legacy_recdir_name_error(int error) +{ +	printk(KERN_ERR "NFSD: unable to generate recoverydir " +			"name (%d).\n", error); + +	/* +	 * if the algorithm just doesn't exist, then disable the recovery +	 * tracker altogether. The crypto libs will generally return this if +	 * FIPS is enabled as well. +	 */ +	if (error == -ENOENT) { +		printk(KERN_ERR "NFSD: disabling legacy clientid tracking. " +			"Reboot recovery will not function correctly!\n"); + +		/* the argument is ignored by the legacy exit function */ +		nfsd4_client_tracking_exit(NULL); +	} +} +  static void  nfsd4_create_clid_dir(struct nfs4_client *clp)  {  	const struct cred *original_cred; -	char *dname = clp->cl_recdir; +	char dname[HEXDIR_LEN];  	struct dentry *dir, *dentry; +	struct nfs4_client_reclaim *crp;  	int status; +	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);  	dprintk("NFSD: nfsd4_create_clid_dir for \"%s\"\n", dname);  	if (test_and_set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))  		return; -	if (!rec_file) +	if (!nn->rec_file)  		return; + +	status = nfs4_make_rec_clidname(dname, &clp->cl_name); +	if (status) +		return legacy_recdir_name_error(status); +  	status = nfs4_save_creds(&original_cred);  	if (status < 0)  		return; -	status = mnt_want_write_file(rec_file); +	status = mnt_want_write_file(nn->rec_file);  	if (status)  		return; -	dir = rec_file->f_path.dentry; +	dir = nn->rec_file->f_path.dentry;  	/* lock the parent */  	mutex_lock(&dir->d_inode->i_mutex); @@ -182,18 +218,24 @@ out_put:  	dput(dentry);  out_unlock:  	mutex_unlock(&dir->d_inode->i_mutex); -	if (status == 0) -		vfs_fsync(rec_file, 0); -	else +	if (status == 0) { +		if (nn->in_grace) { +			crp = nfs4_client_to_reclaim(dname, nn); +			if (crp) +				crp->cr_clp = clp; +		} +		vfs_fsync(nn->rec_file, 0); +	} else {  		printk(KERN_ERR "NFSD: failed to write recovery record"  				" (err %d); please check that %s exists"  				" and is writeable", status,  				user_recovery_dirname); -	mnt_drop_write_file(rec_file); +	} +	mnt_drop_write_file(nn->rec_file);  	nfs4_reset_creds(original_cred);  } -typedef int (recdir_func)(struct dentry *, struct dentry *); +typedef int (recdir_func)(struct dentry *, struct dentry *, struct nfsd_net *);  struct name_list {  	char name[HEXDIR_LEN]; @@ -219,10 +261,10 @@ nfsd4_build_namelist(void *arg, const char *name, int namlen,  }  static int -nfsd4_list_rec_dir(recdir_func *f) +nfsd4_list_rec_dir(recdir_func *f, struct nfsd_net *nn)  {  	const struct cred *original_cred; -	struct dentry *dir = rec_file->f_path.dentry; +	struct dentry *dir = nn->rec_file->f_path.dentry;  	LIST_HEAD(names);  	int status; @@ -230,13 +272,13 @@ nfsd4_list_rec_dir(recdir_func *f)  	if (status < 0)  		return status; -	status = vfs_llseek(rec_file, 0, SEEK_SET); +	status = vfs_llseek(nn->rec_file, 0, SEEK_SET);  	if (status < 0) {  		nfs4_reset_creds(original_cred);  		return status;  	} -	status = vfs_readdir(rec_file, nfsd4_build_namelist, &names); +	status = vfs_readdir(nn->rec_file, nfsd4_build_namelist, &names);  	mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);  	while (!list_empty(&names)) {  		struct name_list *entry; @@ -248,7 +290,7 @@ nfsd4_list_rec_dir(recdir_func *f)  				status = PTR_ERR(dentry);  				break;  			} -			status = f(dir, dentry); +			status = f(dir, dentry, nn);  			dput(dentry);  		}  		list_del(&entry->list); @@ -260,14 +302,14 @@ nfsd4_list_rec_dir(recdir_func *f)  }  static int -nfsd4_unlink_clid_dir(char *name, int namlen) +nfsd4_unlink_clid_dir(char *name, int namlen, struct nfsd_net *nn)  {  	struct dentry *dir, *dentry;  	int status;  	dprintk("NFSD: nfsd4_unlink_clid_dir. name %.*s\n", namlen, name); -	dir = rec_file->f_path.dentry; +	dir = nn->rec_file->f_path.dentry;  	mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);  	dentry = lookup_one_len(name, dir, namlen);  	if (IS_ERR(dentry)) { @@ -289,37 +331,52 @@ static void  nfsd4_remove_clid_dir(struct nfs4_client *clp)  {  	const struct cred *original_cred; +	struct nfs4_client_reclaim *crp; +	char dname[HEXDIR_LEN];  	int status; +	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); -	if (!rec_file || !test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags)) +	if (!nn->rec_file || !test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))  		return; -	status = mnt_want_write_file(rec_file); +	status = nfs4_make_rec_clidname(dname, &clp->cl_name); +	if (status) +		return legacy_recdir_name_error(status); + +	status = mnt_want_write_file(nn->rec_file);  	if (status)  		goto out;  	clear_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);  	status = nfs4_save_creds(&original_cred);  	if (status < 0) -		goto out; +		goto out_drop_write; -	status = nfsd4_unlink_clid_dir(clp->cl_recdir, HEXDIR_LEN-1); +	status = nfsd4_unlink_clid_dir(dname, HEXDIR_LEN-1, nn);  	nfs4_reset_creds(original_cred); -	if (status == 0) -		vfs_fsync(rec_file, 0); -	mnt_drop_write_file(rec_file); +	if (status == 0) { +		vfs_fsync(nn->rec_file, 0); +		if (nn->in_grace) { +			/* remove reclaim record */ +			crp = nfsd4_find_reclaim_client(dname, nn); +			if (crp) +				nfs4_remove_reclaim_record(crp, nn); +		} +	} +out_drop_write: +	mnt_drop_write_file(nn->rec_file);  out:  	if (status)  		printk("NFSD: Failed to remove expired client state directory" -				" %.*s\n", HEXDIR_LEN, clp->cl_recdir); +				" %.*s\n", HEXDIR_LEN, dname);  }  static int -purge_old(struct dentry *parent, struct dentry *child) +purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)  {  	int status; -	if (nfs4_has_reclaimed_state(child->d_name.name, false)) +	if (nfs4_has_reclaimed_state(child->d_name.name, nn))  		return 0;  	status = vfs_rmdir(parent->d_inode, child); @@ -331,27 +388,29 @@ purge_old(struct dentry *parent, struct dentry *child)  }  static void -nfsd4_recdir_purge_old(struct net *net, time_t boot_time) +nfsd4_recdir_purge_old(struct nfsd_net *nn, time_t boot_time)  {  	int status; -	if (!rec_file) +	nn->in_grace = false; +	if (!nn->rec_file)  		return; -	status = mnt_want_write_file(rec_file); +	status = mnt_want_write_file(nn->rec_file);  	if (status)  		goto out; -	status = nfsd4_list_rec_dir(purge_old); +	status = nfsd4_list_rec_dir(purge_old, nn);  	if (status == 0) -		vfs_fsync(rec_file, 0); -	mnt_drop_write_file(rec_file); +		vfs_fsync(nn->rec_file, 0); +	mnt_drop_write_file(nn->rec_file);  out: +	nfs4_release_reclaim(nn);  	if (status)  		printk("nfsd4: failed to purge old clients from recovery" -			" directory %s\n", rec_file->f_path.dentry->d_name.name); +			" directory %s\n", nn->rec_file->f_path.dentry->d_name.name);  }  static int -load_recdir(struct dentry *parent, struct dentry *child) +load_recdir(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)  {  	if (child->d_name.len != HEXDIR_LEN - 1) {  		printk("nfsd4: illegal name %s in recovery directory\n", @@ -359,21 +418,22 @@ load_recdir(struct dentry *parent, struct dentry *child)  		/* Keep trying; maybe the others are OK: */  		return 0;  	} -	nfs4_client_to_reclaim(child->d_name.name); +	nfs4_client_to_reclaim(child->d_name.name, nn);  	return 0;  }  static int -nfsd4_recdir_load(void) { +nfsd4_recdir_load(struct net *net) {  	int status; +	struct nfsd_net *nn =  net_generic(net, nfsd_net_id); -	if (!rec_file) +	if (!nn->rec_file)  		return 0; -	status = nfsd4_list_rec_dir(load_recdir); +	status = nfsd4_list_rec_dir(load_recdir, nn);  	if (status)  		printk("nfsd4: failed loading clients from recovery" -			" directory %s\n", rec_file->f_path.dentry->d_name.name); +			" directory %s\n", nn->rec_file->f_path.dentry->d_name.name);  	return status;  } @@ -382,15 +442,16 @@ nfsd4_recdir_load(void) {   */  static int -nfsd4_init_recdir(void) +nfsd4_init_recdir(struct net *net)  { +	struct nfsd_net *nn = net_generic(net, nfsd_net_id);  	const struct cred *original_cred;  	int status;  	printk("NFSD: Using %s as the NFSv4 state recovery directory\n",  			user_recovery_dirname); -	BUG_ON(rec_file); +	BUG_ON(nn->rec_file);  	status = nfs4_save_creds(&original_cred);  	if (status < 0) { @@ -400,23 +461,65 @@ nfsd4_init_recdir(void)  		return status;  	} -	rec_file = filp_open(user_recovery_dirname, O_RDONLY | O_DIRECTORY, 0); -	if (IS_ERR(rec_file)) { +	nn->rec_file = filp_open(user_recovery_dirname, O_RDONLY | O_DIRECTORY, 0); +	if (IS_ERR(nn->rec_file)) {  		printk("NFSD: unable to find recovery directory %s\n",  				user_recovery_dirname); -		status = PTR_ERR(rec_file); -		rec_file = NULL; +		status = PTR_ERR(nn->rec_file); +		nn->rec_file = NULL;  	}  	nfs4_reset_creds(original_cred); +	if (!status) +		nn->in_grace = true;  	return status;  } + +static int +nfs4_legacy_state_init(struct net *net) +{ +	struct nfsd_net *nn = net_generic(net, nfsd_net_id); +	int i; + +	nn->reclaim_str_hashtbl = kmalloc(sizeof(struct list_head) * +					  CLIENT_HASH_SIZE, GFP_KERNEL); +	if (!nn->reclaim_str_hashtbl) +		return -ENOMEM; + +	for (i = 0; i < CLIENT_HASH_SIZE; i++) +		INIT_LIST_HEAD(&nn->reclaim_str_hashtbl[i]); +	nn->reclaim_str_hashtbl_size = 0; + +	return 0; +} + +static void +nfs4_legacy_state_shutdown(struct net *net) +{ +	struct nfsd_net *nn = net_generic(net, nfsd_net_id); + +	kfree(nn->reclaim_str_hashtbl); +} +  static int  nfsd4_load_reboot_recovery_data(struct net *net)  {  	int status; +	status = nfsd4_init_recdir(net); +	if (!status) +		status = nfsd4_recdir_load(net); +	if (status) +		printk(KERN_ERR "NFSD: Failure reading reboot recovery data\n"); +	return status; +} + +static int +nfsd4_legacy_tracking_init(struct net *net) +{ +	int status; +  	/* XXX: The legacy code won't work in a container */  	if (net != &init_net) {  		WARN(1, KERN_ERR "NFSD: attempt to initialize legacy client " @@ -424,30 +527,37 @@ nfsd4_load_reboot_recovery_data(struct net *net)  		return -EINVAL;  	} -	nfs4_lock_state(); -	status = nfsd4_init_recdir(); -	if (!status) -		status = nfsd4_recdir_load(); -	nfs4_unlock_state(); +	status = nfs4_legacy_state_init(net);  	if (status) -		printk(KERN_ERR "NFSD: Failure reading reboot recovery data\n"); +		return status; + +	status = nfsd4_load_reboot_recovery_data(net); +	if (status) +		goto err; +	return 0; + +err: +	nfs4_legacy_state_shutdown(net);  	return status;  }  static void -nfsd4_shutdown_recdir(void) +nfsd4_shutdown_recdir(struct nfsd_net *nn)  { -	if (!rec_file) +	if (!nn->rec_file)  		return; -	fput(rec_file); -	rec_file = NULL; +	fput(nn->rec_file); +	nn->rec_file = NULL;  }  static void  nfsd4_legacy_tracking_exit(struct net *net)  { -	nfs4_release_reclaim(); -	nfsd4_shutdown_recdir(); +	struct nfsd_net *nn = net_generic(net, nfsd_net_id); + +	nfs4_release_reclaim(nn); +	nfsd4_shutdown_recdir(nn); +	nfs4_legacy_state_shutdown(net);  }  /* @@ -480,13 +590,26 @@ nfs4_recoverydir(void)  static int  nfsd4_check_legacy_client(struct nfs4_client *clp)  { +	int status; +	char dname[HEXDIR_LEN]; +	struct nfs4_client_reclaim *crp; +	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); +  	/* did we already find that this client is stable? */  	if (test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))  		return 0; +	status = nfs4_make_rec_clidname(dname, &clp->cl_name); +	if (status) { +		legacy_recdir_name_error(status); +		return status; +	} +  	/* look for it in the reclaim hashtable otherwise */ -	if (nfsd4_find_reclaim_client(clp)) { +	crp = nfsd4_find_reclaim_client(dname, nn); +	if (crp) {  		set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags); +		crp->cr_clp = clp;  		return 0;  	} @@ -494,7 +617,7 @@ nfsd4_check_legacy_client(struct nfs4_client *clp)  }  static struct nfsd4_client_tracking_ops nfsd4_legacy_tracking_ops = { -	.init		= nfsd4_load_reboot_recovery_data, +	.init		= nfsd4_legacy_tracking_init,  	.exit		= nfsd4_legacy_tracking_exit,  	.create		= nfsd4_create_clid_dir,  	.remove		= nfsd4_remove_clid_dir, @@ -785,8 +908,7 @@ 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 nfsd_net *nn = net_generic(clp->net, nfsd_net_id);  	struct cld_net *cn = nn->cld_net;  	/* Don't upcall if it's already stored */ @@ -823,8 +945,7 @@ 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 nfsd_net *nn = net_generic(clp->net, nfsd_net_id);  	struct cld_net *cn = nn->cld_net;  	/* Don't upcall if it's already removed */ @@ -861,8 +982,7 @@ 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 nfsd_net *nn = net_generic(clp->net, nfsd_net_id);  	struct cld_net *cn = nn->cld_net;  	/* Don't upcall if one was already stored during this grace pd */ @@ -892,11 +1012,10 @@ nfsd4_cld_check(struct nfs4_client *clp)  }  static void -nfsd4_cld_grace_done(struct net *net, time_t boot_time) +nfsd4_cld_grace_done(struct nfsd_net *nn, 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); @@ -926,28 +1045,261 @@ static struct nfsd4_client_tracking_ops nfsd4_cld_tracking_ops = {  	.grace_done	= nfsd4_cld_grace_done,  }; +/* upcall via usermodehelper */ +static char cltrack_prog[PATH_MAX] = "/sbin/nfsdcltrack"; +module_param_string(cltrack_prog, cltrack_prog, sizeof(cltrack_prog), +			S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(cltrack_prog, "Path to the nfsdcltrack upcall program"); + +static bool cltrack_legacy_disable; +module_param(cltrack_legacy_disable, bool, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(cltrack_legacy_disable, +		"Disable legacy recoverydir conversion. Default: false"); + +#define LEGACY_TOPDIR_ENV_PREFIX "NFSDCLTRACK_LEGACY_TOPDIR=" +#define LEGACY_RECDIR_ENV_PREFIX "NFSDCLTRACK_LEGACY_RECDIR=" + +static char * +nfsd4_cltrack_legacy_topdir(void) +{ +	int copied; +	size_t len; +	char *result; + +	if (cltrack_legacy_disable) +		return NULL; + +	len = strlen(LEGACY_TOPDIR_ENV_PREFIX) + +		strlen(nfs4_recoverydir()) + 1; + +	result = kmalloc(len, GFP_KERNEL); +	if (!result) +		return result; + +	copied = snprintf(result, len, LEGACY_TOPDIR_ENV_PREFIX "%s", +				nfs4_recoverydir()); +	if (copied >= len) { +		/* just return nothing if output was truncated */ +		kfree(result); +		return NULL; +	} + +	return result; +} + +static char * +nfsd4_cltrack_legacy_recdir(const struct xdr_netobj *name) +{ +	int copied; +	size_t len; +	char *result; + +	if (cltrack_legacy_disable) +		return NULL; + +	/* +1 is for '/' between "topdir" and "recdir" */ +	len = strlen(LEGACY_RECDIR_ENV_PREFIX) + +		strlen(nfs4_recoverydir()) + 1 + HEXDIR_LEN; + +	result = kmalloc(len, GFP_KERNEL); +	if (!result) +		return result; + +	copied = snprintf(result, len, LEGACY_RECDIR_ENV_PREFIX "%s/", +				nfs4_recoverydir()); +	if (copied > (len - HEXDIR_LEN)) { +		/* just return nothing if output will be truncated */ +		kfree(result); +		return NULL; +	} + +	copied = nfs4_make_rec_clidname(result + copied, name); +	if (copied) { +		kfree(result); +		return NULL; +	} + +	return result; +} + +static int +nfsd4_umh_cltrack_upcall(char *cmd, char *arg, char *legacy) +{ +	char *envp[2]; +	char *argv[4]; +	int ret; + +	if (unlikely(!cltrack_prog[0])) { +		dprintk("%s: cltrack_prog is disabled\n", __func__); +		return -EACCES; +	} + +	dprintk("%s: cmd: %s\n", __func__, cmd); +	dprintk("%s: arg: %s\n", __func__, arg ? arg : "(null)"); +	dprintk("%s: legacy: %s\n", __func__, legacy ? legacy : "(null)"); + +	envp[0] = legacy; +	envp[1] = NULL; + +	argv[0] = (char *)cltrack_prog; +	argv[1] = cmd; +	argv[2] = arg; +	argv[3] = NULL; + +	ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC); +	/* +	 * Disable the upcall mechanism if we're getting an ENOENT or EACCES +	 * error. The admin can re-enable it on the fly by using sysfs +	 * once the problem has been fixed. +	 */ +	if (ret == -ENOENT || ret == -EACCES) { +		dprintk("NFSD: %s was not found or isn't executable (%d). " +			"Setting cltrack_prog to blank string!", +			cltrack_prog, ret); +		cltrack_prog[0] = '\0'; +	} +	dprintk("%s: %s return value: %d\n", __func__, cltrack_prog, ret); + +	return ret; +} + +static char * +bin_to_hex_dup(const unsigned char *src, int srclen) +{ +	int i; +	char *buf, *hex; + +	/* +1 for terminating NULL */ +	buf = kmalloc((srclen * 2) + 1, GFP_KERNEL); +	if (!buf) +		return buf; + +	hex = buf; +	for (i = 0; i < srclen; i++) { +		sprintf(hex, "%2.2x", *src++); +		hex += 2; +	} +	return buf; +} + +static int +nfsd4_umh_cltrack_init(struct net __attribute__((unused)) *net) +{ +	return nfsd4_umh_cltrack_upcall("init", NULL, NULL); +} + +static void +nfsd4_umh_cltrack_create(struct nfs4_client *clp) +{ +	char *hexid; + +	hexid = bin_to_hex_dup(clp->cl_name.data, clp->cl_name.len); +	if (!hexid) { +		dprintk("%s: can't allocate memory for upcall!\n", __func__); +		return; +	} +	nfsd4_umh_cltrack_upcall("create", hexid, NULL); +	kfree(hexid); +} + +static void +nfsd4_umh_cltrack_remove(struct nfs4_client *clp) +{ +	char *hexid; + +	hexid = bin_to_hex_dup(clp->cl_name.data, clp->cl_name.len); +	if (!hexid) { +		dprintk("%s: can't allocate memory for upcall!\n", __func__); +		return; +	} +	nfsd4_umh_cltrack_upcall("remove", hexid, NULL); +	kfree(hexid); +} + +static int +nfsd4_umh_cltrack_check(struct nfs4_client *clp) +{ +	int ret; +	char *hexid, *legacy; + +	hexid = bin_to_hex_dup(clp->cl_name.data, clp->cl_name.len); +	if (!hexid) { +		dprintk("%s: can't allocate memory for upcall!\n", __func__); +		return -ENOMEM; +	} +	legacy = nfsd4_cltrack_legacy_recdir(&clp->cl_name); +	ret = nfsd4_umh_cltrack_upcall("check", hexid, legacy); +	kfree(legacy); +	kfree(hexid); +	return ret; +} + +static void +nfsd4_umh_cltrack_grace_done(struct nfsd_net __attribute__((unused)) *nn, +				time_t boot_time) +{ +	char *legacy; +	char timestr[22]; /* FIXME: better way to determine max size? */ + +	sprintf(timestr, "%ld", boot_time); +	legacy = nfsd4_cltrack_legacy_topdir(); +	nfsd4_umh_cltrack_upcall("gracedone", timestr, legacy); +	kfree(legacy); +} + +static struct nfsd4_client_tracking_ops nfsd4_umh_tracking_ops = { +	.init		= nfsd4_umh_cltrack_init, +	.exit		= NULL, +	.create		= nfsd4_umh_cltrack_create, +	.remove		= nfsd4_umh_cltrack_remove, +	.check		= nfsd4_umh_cltrack_check, +	.grace_done	= nfsd4_umh_cltrack_grace_done, +}; +  int  nfsd4_client_tracking_init(struct net *net)  {  	int status;  	struct path path; +	struct nfsd_net *nn = net_generic(net, nfsd_net_id); -	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); -		} +	/* just run the init if it the method is already decided */ +	if (nn->client_tracking_ops) +		goto do_init; + +	/* +	 * First, try a UMH upcall. It should succeed or fail quickly, so +	 * there's little harm in trying that first. +	 */ +	nn->client_tracking_ops = &nfsd4_umh_tracking_ops; +	status = nn->client_tracking_ops->init(net); +	if (!status) +		return status; + +	/* +	 * See if the recoverydir exists and is a directory. If it is, +	 * then use the legacy ops. +	 */ +	nn->client_tracking_ops = &nfsd4_legacy_tracking_ops; +	status = kern_path(nfs4_recoverydir(), LOOKUP_FOLLOW, &path); +	if (!status) { +		status = S_ISDIR(path.dentry->d_inode->i_mode); +		path_put(&path); +		if (status) +			goto do_init;  	} -	status = client_tracking_ops->init(net); +	/* Finally, try to use nfsdcld */ +	nn->client_tracking_ops = &nfsd4_cld_tracking_ops; +	printk(KERN_WARNING "NFSD: the nfsdcld client tracking upcall will be " +			"removed in 3.10. Please transition to using " +			"nfsdcltrack.\n"); +do_init: +	status = nn->client_tracking_ops->init(net);  	if (status) {  		printk(KERN_WARNING "NFSD: Unable to initialize client "  				    "recovery tracking! (%d)\n", status); -		client_tracking_ops = NULL; +		nn->client_tracking_ops = NULL;  	}  	return status;  } @@ -955,40 +1307,49 @@ nfsd4_client_tracking_init(struct net *net)  void  nfsd4_client_tracking_exit(struct net *net)  { -	if (client_tracking_ops) { -		client_tracking_ops->exit(net); -		client_tracking_ops = NULL; +	struct nfsd_net *nn = net_generic(net, nfsd_net_id); + +	if (nn->client_tracking_ops) { +		if (nn->client_tracking_ops->exit) +			nn->client_tracking_ops->exit(net); +		nn->client_tracking_ops = NULL;  	}  }  void  nfsd4_client_record_create(struct nfs4_client *clp)  { -	if (client_tracking_ops) -		client_tracking_ops->create(clp); +	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); + +	if (nn->client_tracking_ops) +		nn->client_tracking_ops->create(clp);  }  void  nfsd4_client_record_remove(struct nfs4_client *clp)  { -	if (client_tracking_ops) -		client_tracking_ops->remove(clp); +	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); + +	if (nn->client_tracking_ops) +		nn->client_tracking_ops->remove(clp);  }  int  nfsd4_client_record_check(struct nfs4_client *clp)  { -	if (client_tracking_ops) -		return client_tracking_ops->check(clp); +	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); + +	if (nn->client_tracking_ops) +		return nn->client_tracking_ops->check(clp);  	return -EOPNOTSUPP;  }  void -nfsd4_record_grace_done(struct net *net, time_t boot_time) +nfsd4_record_grace_done(struct nfsd_net *nn, time_t boot_time)  { -	if (client_tracking_ops) -		client_tracking_ops->grace_done(net, boot_time); +	if (nn->client_tracking_ops) +		nn->client_tracking_ops->grace_done(nn, boot_time);  }  static int  |