Merge git://git.kernel.org/pub/scm/linux/kernel/git/sage/ceph-client

* git://git.kernel.org/pub/scm/linux/kernel/git/sage/ceph-client:
  libceph: Create a new key type "ceph".
  libceph: Get secret from the kernel keys api when mounting with key=NAME.
  ceph: Move secret key parsing earlier.
  libceph: fix null dereference when unregistering linger requests
  ceph: unlock on error in ceph_osdc_start_request()
  ceph: fix possible NULL pointer dereference
  ceph: flush msgr_wq during mds_client shutdown
diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c
index a1ee8fa..f60b07b 100644
--- a/fs/ceph/mds_client.c
+++ b/fs/ceph/mds_client.c
@@ -3215,9 +3215,15 @@
 {
 	struct ceph_mds_client *mdsc = fsc->mdsc;
 
+	dout("mdsc_destroy %p\n", mdsc);
 	ceph_mdsc_stop(mdsc);
+
+	/* flush out any connection work with references to us */
+	ceph_msgr_flush();
+
 	fsc->mdsc = NULL;
 	kfree(mdsc);
+	dout("mdsc_destroy %p done\n", mdsc);
 }
 
 
diff --git a/fs/ceph/super.c b/fs/ceph/super.c
index a9e78b4..f2f77fd 100644
--- a/fs/ceph/super.c
+++ b/fs/ceph/super.c
@@ -353,7 +353,7 @@
 
 	if (opt->name)
 		seq_printf(m, ",name=%s", opt->name);
-	if (opt->secret)
+	if (opt->key)
 		seq_puts(m, ",secret=<hidden>");
 
 	if (opt->mount_timeout != CEPH_MOUNT_TIMEOUT_DEFAULT)
diff --git a/include/keys/ceph-type.h b/include/keys/ceph-type.h
new file mode 100644
index 0000000..f69c4ac
--- /dev/null
+++ b/include/keys/ceph-type.h
@@ -0,0 +1,8 @@
+#ifndef _KEYS_CEPH_TYPE_H
+#define _KEYS_CEPH_TYPE_H
+
+#include <linux/key.h>
+
+extern struct key_type key_type_ceph;
+
+#endif
diff --git a/include/linux/ceph/auth.h b/include/linux/ceph/auth.h
index 7fff521..aa13392 100644
--- a/include/linux/ceph/auth.h
+++ b/include/linux/ceph/auth.h
@@ -67,12 +67,12 @@
 	bool negotiating;       /* true if negotiating protocol */
 	const char *name;       /* entity name */
 	u64 global_id;          /* our unique id in system */
-	const char *secret;     /* our secret key */
+	const struct ceph_crypto_key *key;     /* our secret key */
 	unsigned want_keys;     /* which services we want */
 };
 
 extern struct ceph_auth_client *ceph_auth_init(const char *name,
-					       const char *secret);
+					       const struct ceph_crypto_key *key);
 extern void ceph_auth_destroy(struct ceph_auth_client *ac);
 
 extern void ceph_auth_reset(struct ceph_auth_client *ac);
diff --git a/include/linux/ceph/libceph.h b/include/linux/ceph/libceph.h
index 0d2e0ff..6365f04 100644
--- a/include/linux/ceph/libceph.h
+++ b/include/linux/ceph/libceph.h
@@ -61,7 +61,7 @@
 					      pointer type of args */
 	int num_mon;
 	char *name;
-	char *secret;
+	struct ceph_crypto_key *key;
 };
 
 /*
diff --git a/net/ceph/Kconfig b/net/ceph/Kconfig
index ad42404..be683f2 100644
--- a/net/ceph/Kconfig
+++ b/net/ceph/Kconfig
@@ -4,6 +4,7 @@
 	select LIBCRC32C
 	select CRYPTO_AES
 	select CRYPTO
+	select KEYS
 	default n
 	help
 	  Choose Y or M here to include cephlib, which provides the
diff --git a/net/ceph/auth.c b/net/ceph/auth.c
index 549c1f4..b4bf4ac 100644
--- a/net/ceph/auth.c
+++ b/net/ceph/auth.c
@@ -35,12 +35,12 @@
 /*
  * setup, teardown.
  */
-struct ceph_auth_client *ceph_auth_init(const char *name, const char *secret)
+struct ceph_auth_client *ceph_auth_init(const char *name, const struct ceph_crypto_key *key)
 {
 	struct ceph_auth_client *ac;
 	int ret;
 
-	dout("auth_init name '%s' secret '%s'\n", name, secret);
+	dout("auth_init name '%s'\n", name);
 
 	ret = -ENOMEM;
 	ac = kzalloc(sizeof(*ac), GFP_NOFS);
@@ -52,8 +52,8 @@
 		ac->name = name;
 	else
 		ac->name = CEPH_AUTH_NAME_DEFAULT;
-	dout("auth_init name %s secret %s\n", ac->name, secret);
-	ac->secret = secret;
+	dout("auth_init name %s\n", ac->name);
+	ac->key = key;
 	return ac;
 
 out:
diff --git a/net/ceph/auth_x.c b/net/ceph/auth_x.c
index 7fd5dfc..1587dc6 100644
--- a/net/ceph/auth_x.c
+++ b/net/ceph/auth_x.c
@@ -662,14 +662,16 @@
 		goto out;
 
 	ret = -EINVAL;
-	if (!ac->secret) {
+	if (!ac->key) {
 		pr_err("no secret set (for auth_x protocol)\n");
 		goto out_nomem;
 	}
 
-	ret = ceph_crypto_key_unarmor(&xi->secret, ac->secret);
-	if (ret)
+	ret = ceph_crypto_key_clone(&xi->secret, ac->key);
+	if (ret < 0) {
+		pr_err("cannot clone key: %d\n", ret);
 		goto out_nomem;
+	}
 
 	xi->starting = true;
 	xi->ticket_handlers = RB_ROOT;
diff --git a/net/ceph/ceph_common.c b/net/ceph/ceph_common.c
index 95f96ab..132963a 100644
--- a/net/ceph/ceph_common.c
+++ b/net/ceph/ceph_common.c
@@ -5,6 +5,8 @@
 #include <linux/fs.h>
 #include <linux/inet.h>
 #include <linux/in6.h>
+#include <linux/key.h>
+#include <keys/ceph-type.h>
 #include <linux/module.h>
 #include <linux/mount.h>
 #include <linux/parser.h>
@@ -20,6 +22,7 @@
 #include <linux/ceph/decode.h>
 #include <linux/ceph/mon_client.h>
 #include <linux/ceph/auth.h>
+#include "crypto.h"
 
 
 
@@ -117,9 +120,29 @@
 	if (ret)
 		return ret;
 
-	ret = strcmp_null(opt1->secret, opt2->secret);
-	if (ret)
-		return ret;
+	if (opt1->key && !opt2->key)
+		return -1;
+	if (!opt1->key && opt2->key)
+		return 1;
+	if (opt1->key && opt2->key) {
+		if (opt1->key->type != opt2->key->type)
+			return -1;
+		if (opt1->key->created.tv_sec != opt2->key->created.tv_sec)
+			return -1;
+		if (opt1->key->created.tv_nsec != opt2->key->created.tv_nsec)
+			return -1;
+		if (opt1->key->len != opt2->key->len)
+			return -1;
+		if (opt1->key->key && !opt2->key->key)
+			return -1;
+		if (!opt1->key->key && opt2->key->key)
+			return 1;
+		if (opt1->key->key && opt2->key->key) {
+			ret = memcmp(opt1->key->key, opt2->key->key, opt1->key->len);
+			if (ret)
+				return ret;
+		}
+	}
 
 	/* any matching mon ip implies a match */
 	for (i = 0; i < opt1->num_mon; i++) {
@@ -176,6 +199,7 @@
 	Opt_fsid,
 	Opt_name,
 	Opt_secret,
+	Opt_key,
 	Opt_ip,
 	Opt_last_string,
 	/* string args above */
@@ -192,6 +216,7 @@
 	{Opt_fsid, "fsid=%s"},
 	{Opt_name, "name=%s"},
 	{Opt_secret, "secret=%s"},
+	{Opt_key, "key=%s"},
 	{Opt_ip, "ip=%s"},
 	/* string args above */
 	{Opt_noshare, "noshare"},
@@ -203,11 +228,56 @@
 {
 	dout("destroy_options %p\n", opt);
 	kfree(opt->name);
-	kfree(opt->secret);
+	if (opt->key) {
+		ceph_crypto_key_destroy(opt->key);
+		kfree(opt->key);
+	}
 	kfree(opt);
 }
 EXPORT_SYMBOL(ceph_destroy_options);
 
+/* get secret from key store */
+static int get_secret(struct ceph_crypto_key *dst, const char *name) {
+	struct key *ukey;
+	int key_err;
+	int err = 0;
+	struct ceph_crypto_key *ckey;
+
+	ukey = request_key(&key_type_ceph, name, NULL);
+	if (!ukey || IS_ERR(ukey)) {
+		/* request_key errors don't map nicely to mount(2)
+		   errors; don't even try, but still printk */
+		key_err = PTR_ERR(ukey);
+		switch (key_err) {
+		case -ENOKEY:
+			pr_warning("ceph: Mount failed due to key not found: %s\n", name);
+			break;
+		case -EKEYEXPIRED:
+			pr_warning("ceph: Mount failed due to expired key: %s\n", name);
+			break;
+		case -EKEYREVOKED:
+			pr_warning("ceph: Mount failed due to revoked key: %s\n", name);
+			break;
+		default:
+			pr_warning("ceph: Mount failed due to unknown key error"
+			       " %d: %s\n", key_err, name);
+		}
+		err = -EPERM;
+		goto out;
+	}
+
+	ckey = ukey->payload.data;
+	err = ceph_crypto_key_clone(dst, ckey);
+	if (err)
+		goto out_key;
+	/* pass through, err is 0 */
+
+out_key:
+	key_put(ukey);
+out:
+	return err;
+}
+
 int ceph_parse_options(struct ceph_options **popt, char *options,
 		       const char *dev_name, const char *dev_name_end,
 		       int (*parse_extra_token)(char *c, void *private),
@@ -295,9 +365,24 @@
 					      GFP_KERNEL);
 			break;
 		case Opt_secret:
-			opt->secret = kstrndup(argstr[0].from,
-						argstr[0].to-argstr[0].from,
-						GFP_KERNEL);
+		        opt->key = kzalloc(sizeof(*opt->key), GFP_KERNEL);
+			if (!opt->key) {
+				err = -ENOMEM;
+				goto out;
+			}
+			err = ceph_crypto_key_unarmor(opt->key, argstr[0].from);
+			if (err < 0)
+				goto out;
+			break;
+		case Opt_key:
+		        opt->key = kzalloc(sizeof(*opt->key), GFP_KERNEL);
+			if (!opt->key) {
+				err = -ENOMEM;
+				goto out;
+			}
+			err = get_secret(opt->key, argstr[0].from);
+			if (err < 0)
+				goto out;
 			break;
 
 			/* misc */
@@ -394,8 +479,8 @@
 	ceph_osdc_stop(&client->osdc);
 
 	/*
-	 * make sure mds and osd connections close out before destroying
-	 * the auth module, which is needed to free those connections'
+	 * make sure osd connections close out before destroying the
+	 * auth module, which is needed to free those connections'
 	 * ceph_authorizers.
 	 */
 	ceph_msgr_flush();
@@ -496,10 +581,14 @@
 	if (ret < 0)
 		goto out;
 
-	ret = ceph_msgr_init();
+	ret = ceph_crypto_init();
 	if (ret < 0)
 		goto out_debugfs;
 
+	ret = ceph_msgr_init();
+	if (ret < 0)
+		goto out_crypto;
+
 	pr_info("loaded (mon/osd proto %d/%d, osdmap %d/%d %d/%d)\n",
 		CEPH_MONC_PROTOCOL, CEPH_OSDC_PROTOCOL,
 		CEPH_OSDMAP_VERSION, CEPH_OSDMAP_VERSION_EXT,
@@ -507,6 +596,8 @@
 
 	return 0;
 
+out_crypto:
+	ceph_crypto_shutdown();
 out_debugfs:
 	ceph_debugfs_cleanup();
 out:
@@ -517,6 +608,7 @@
 {
 	dout("exit_ceph_lib\n");
 	ceph_msgr_exit();
+	ceph_crypto_shutdown();
 	ceph_debugfs_cleanup();
 }
 
diff --git a/net/ceph/crypto.c b/net/ceph/crypto.c
index 7b505b0..5a8009c 100644
--- a/net/ceph/crypto.c
+++ b/net/ceph/crypto.c
@@ -5,10 +5,23 @@
 #include <linux/scatterlist.h>
 #include <linux/slab.h>
 #include <crypto/hash.h>
+#include <linux/key-type.h>
 
+#include <keys/ceph-type.h>
 #include <linux/ceph/decode.h>
 #include "crypto.h"
 
+int ceph_crypto_key_clone(struct ceph_crypto_key *dst,
+			  const struct ceph_crypto_key *src)
+{
+	memcpy(dst, src, sizeof(struct ceph_crypto_key));
+	dst->key = kmalloc(src->len, GFP_NOFS);
+	if (!dst->key)
+		return -ENOMEM;
+	memcpy(dst->key, src->key, src->len);
+	return 0;
+}
+
 int ceph_crypto_key_encode(struct ceph_crypto_key *key, void **p, void *end)
 {
 	if (*p + sizeof(u16) + sizeof(key->created) +
@@ -410,3 +423,63 @@
 		return -EINVAL;
 	}
 }
+
+int ceph_key_instantiate(struct key *key, const void *data, size_t datalen)
+{
+	struct ceph_crypto_key *ckey;
+	int ret;
+	void *p;
+
+	ret = -EINVAL;
+	if (datalen <= 0 || datalen > 32767 || !data)
+		goto err;
+
+	ret = key_payload_reserve(key, datalen);
+	if (ret < 0)
+		goto err;
+
+	ret = -ENOMEM;
+	ckey = kmalloc(sizeof(*ckey), GFP_KERNEL);
+	if (!ckey)
+		goto err;
+
+	/* TODO ceph_crypto_key_decode should really take const input */
+	p = (void*)data;
+	ret = ceph_crypto_key_decode(ckey, &p, (char*)data+datalen);
+	if (ret < 0)
+		goto err_ckey;
+
+	key->payload.data = ckey;
+	return 0;
+
+err_ckey:
+	kfree(ckey);
+err:
+	return ret;
+}
+
+int ceph_key_match(const struct key *key, const void *description)
+{
+	return strcmp(key->description, description) == 0;
+}
+
+void ceph_key_destroy(struct key *key) {
+	struct ceph_crypto_key *ckey = key->payload.data;
+
+	ceph_crypto_key_destroy(ckey);
+}
+
+struct key_type key_type_ceph = {
+	.name		= "ceph",
+	.instantiate	= ceph_key_instantiate,
+	.match		= ceph_key_match,
+	.destroy	= ceph_key_destroy,
+};
+
+int ceph_crypto_init(void) {
+	return register_key_type(&key_type_ceph);
+}
+
+void ceph_crypto_shutdown(void) {
+	unregister_key_type(&key_type_ceph);
+}
diff --git a/net/ceph/crypto.h b/net/ceph/crypto.h
index f9eccac..1919d15 100644
--- a/net/ceph/crypto.h
+++ b/net/ceph/crypto.h
@@ -19,6 +19,8 @@
 	kfree(key->key);
 }
 
+extern int ceph_crypto_key_clone(struct ceph_crypto_key *dst,
+				 const struct ceph_crypto_key *src);
 extern int ceph_crypto_key_encode(struct ceph_crypto_key *key,
 				  void **p, void *end);
 extern int ceph_crypto_key_decode(struct ceph_crypto_key *key,
@@ -40,6 +42,8 @@
 			 void *dst, size_t *dst_len,
 			 const void *src1, size_t src1_len,
 			 const void *src2, size_t src2_len);
+extern int ceph_crypto_init(void);
+extern void ceph_crypto_shutdown(void);
 
 /* armor.c */
 extern int ceph_armor(char *dst, const char *src, const char *end);
diff --git a/net/ceph/mon_client.c b/net/ceph/mon_client.c
index 8a07939..cbe31fa 100644
--- a/net/ceph/mon_client.c
+++ b/net/ceph/mon_client.c
@@ -759,7 +759,7 @@
 
 	/* authentication */
 	monc->auth = ceph_auth_init(cl->options->name,
-				    cl->options->secret);
+				    cl->options->key);
 	if (IS_ERR(monc->auth))
 		return PTR_ERR(monc->auth);
 	monc->auth->want_keys =
diff --git a/net/ceph/osd_client.c b/net/ceph/osd_client.c
index 02212ed..3b91d65 100644
--- a/net/ceph/osd_client.c
+++ b/net/ceph/osd_client.c
@@ -837,8 +837,7 @@
 			dout("moving osd to %p lru\n", req->r_osd);
 			__move_osd_to_lru(osdc, req->r_osd);
 		}
-		if (list_empty(&req->r_osd_item) &&
-		    list_empty(&req->r_linger_item))
+		if (list_empty(&req->r_linger_item))
 			req->r_osd = NULL;
 	}
 
@@ -883,7 +882,8 @@
 			dout("moving osd to %p lru\n", req->r_osd);
 			__move_osd_to_lru(osdc, req->r_osd);
 		}
-		req->r_osd = NULL;
+		if (list_empty(&req->r_osd_item))
+			req->r_osd = NULL;
 	}
 }
 
@@ -1602,11 +1602,11 @@
 	     cookie, ver, event);
 	if (event) {
 		event_work = kmalloc(sizeof(*event_work), GFP_NOIO);
-		INIT_WORK(&event_work->work, do_event_work);
 		if (!event_work) {
 			dout("ERROR: could not allocate event_work\n");
 			goto done_err;
 		}
+		INIT_WORK(&event_work->work, do_event_work);
 		event_work->event = event;
 		event_work->ver = ver;
 		event_work->notify_id = notify_id;
@@ -1672,7 +1672,7 @@
 	if (req->r_sent == 0) {
 		rc = __map_request(osdc, req);
 		if (rc < 0)
-			return rc;
+			goto out_unlock;
 		if (req->r_osd == NULL) {
 			dout("send_request %p no up osds in pg\n", req);
 			ceph_monc_request_next_osdmap(&osdc->client->monc);
@@ -1689,6 +1689,8 @@
 			}
 		}
 	}
+
+out_unlock:
 	mutex_unlock(&osdc->request_mutex);
 	up_read(&osdc->map_sem);
 	return rc;