From: Mike Anderson <[email-masked]>

This patch adds support for the dm_path_event dm_send_event funtions which
create and send netlink attribute events.

Signed-off-by: Mike Anderson <[email-masked]>
---

 drivers/md/dm-netlink.c    |  209 ++++++++++++++++++++++++++++++++++++++++++++-
 drivers/md/dm-netlink.h    |   25 +++--
 drivers/md/dm.c            |   21 ++++
 include/linux/Kbuild       |    1 
 include/linux/dm-netlink.h |   59 ++++++++++++
 5 files changed, 306 insertions(+), 9 deletions(-)

Index: linux-2.6.21-rc5/drivers/md/dm-netlink.c
===================================================================
--- linux-2.6.21-rc5.orig/drivers/md/dm-netlink.c	2007-04-04 19:28:59.000000000 +0100
+++ linux-2.6.21-rc5/drivers/md/dm-netlink.c	2007-04-04 19:29:01.000000000 +0100
@@ -40,6 +40,20 @@ struct dm_event_cache {
 
 static struct dm_event_cache _dme_cache;
 
+struct dm_event {
+        struct dm_event_cache *cdata;
+        struct mapped_device *md;
+        struct sk_buff *skb;
+        struct list_head elist;
+};
+
+static struct sock *_dm_netlink_sock;
+static uint32_t _dm_netlink_daemon_pid;
+static DEFINE_SPINLOCK(_dm_netlink_pid_lock);
+
+static uint64_t _dm_netlink_event_seqnum;
+static DEFINE_SPINLOCK(_dm_netlink_sequence_lock);
+
 static int dme_cache_init(struct dm_event_cache *dc, unsigned skb_size)
 {
 	dc->skb_size = skb_size;
@@ -86,13 +100,202 @@ cache_err:
 	return NULL;
 }
 
+static struct dm_event *dm_netlink_build_path_msg(struct mapped_device *md,
+						  const char *path, int type,
+						  int nr_valid_paths)
+{
+	struct dm_event *evt;
+	struct nlmsghdr	*nlh;
+	struct dm_netlink_msghdr *nlhdr;
+	uint64_t seq;
+	struct timeval tv;
+	int r = -ENOBUFS;
+
+	evt = dme_cache_event_get(&_dme_cache, md);
+	if (!evt) {
+		DMERR("%s: dme_cache_event_get() failed", __FUNCTION__);
+		r = -ENOMEM;
+		goto error;
+	}
+
+	nlh = nlmsg_put(evt->skb, 0, 0, DM_EVENT_MSG,
+			NLMSG_ALIGN(sizeof(*nlhdr)), 0);
+	if (!nlh) {
+		DMERR("%s: nlmsg_put() failed", __FUNCTION__);
+		goto nlmsg_put_failure;
+	}
+
+	nlhdr = nlmsg_data(nlh);
+	nlhdr->type = type;
+	nlhdr->version = DM_EVENT_ATTR_MAX;
+
+	spin_lock(&_dm_netlink_sequence_lock);
+	seq = ++_dm_netlink_event_seqnum;
+	spin_unlock(&_dm_netlink_sequence_lock);
+	do_gettimeofday(&tv);
+
+	NLA_PUT_U64(evt->skb, DM_EVENT_ATTR_SEQNUM, seq);
+	NLA_PUT_U64(evt->skb, DM_EVENT_ATTR_TSSEC, tv.tv_sec);
+	NLA_PUT_U64(evt->skb, DM_EVENT_ATTR_TSUSEC, tv.tv_usec);
+	NLA_PUT_STRING(evt->skb, DM_EVENT_ATTR_PATH, path);
+	NLA_PUT_U32(evt->skb, DM_EVENT_ATTR_NUM_VALID_PATHS, nr_valid_paths);
+
+	return evt;
+
+nla_put_failure:
+	DMERR("%s: nla_put_failure", __FUNCTION__);
+nlmsg_put_failure:
+	nlmsg_free(evt->skb);
+	dme_cache_event_put(evt);
+error:
+	return ERR_PTR(r);
+}
+
+static void dm_send_event(struct dm_event *evt)
+{
+	struct nlmsghdr *nlh = (struct nlmsghdr *) evt->skb->data;
+	int r;
+
+	NLA_PUT_STRING(evt->skb, DM_EVENT_ATTR_NAME, dm_device_name(evt->md));
+	nlmsg_end(evt->skb, nlh);
+
+	spin_lock(&_dm_netlink_pid_lock);
+	nlh->nlmsg_pid = _dm_netlink_daemon_pid;
+	spin_unlock(&_dm_netlink_pid_lock);
+
+	if (!nlh->nlmsg_pid)
+		goto no_pid;
+
+	r = nlmsg_unicast(_dm_netlink_sock, evt->skb, nlh->nlmsg_pid);
+	if (r && (r != -ESRCH))
+		DMERR("%s: nlmsg_unicast() failed: %d", __FUNCTION__, r);
+	goto out;
+
+nla_put_failure:
+	DMERR("%s: nla_put_failure", __FUNCTION__);
+no_pid:
+	nlmsg_free(evt->skb);
+out:
+	dme_cache_event_put(evt);
+}
+
+void dm_netlink_send_events(struct list_head *events)
+{
+        struct dm_event *evt, *next;
+
+        list_for_each_entry_safe(evt, next, events, elist) {
+                list_del_init(&evt->elist);
+                dm_send_event(evt);
+        }
+}
+EXPORT_SYMBOL_GPL(dm_netlink_send_events);
+
+/**
+ * dm_path_event - called to create a new path event and queue it
+ *
+ * @evt_type:		enum of path event type
+ * @t:			pointer to a dm_table
+ * @path:		string containing pathname
+ * @nr_valid_paths:	number of valid paths remaining
+ *
+ **/
+void dm_path_event(enum dm_netlink_event_type evt_type, struct dm_table *t,
+		   const char *path, int nr_valid_paths)
+{
+	struct mapped_device *md = dm_table_get_md(t);
+	struct dm_event *evt = dm_netlink_build_path_msg(md, path, evt_type,
+							 nr_valid_paths);
+
+	if (IS_ERR(evt))
+		goto out;
+
+	dm_event_add(md, &evt->elist);
+out:
+	dm_put(md);
+}
+EXPORT_SYMBOL_GPL(dm_path_event);
+
+static int dm_netlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
+			       int *errp)
+{
+	*errp = 0;
+
+	if (security_netlink_recv(skb, CAP_SYS_ADMIN)) {
+		*errp = -EPERM;
+		goto out;
+	}
+
+	spin_lock(&_dm_netlink_pid_lock);
+	if (_dm_netlink_daemon_pid) {
+		if (_dm_netlink_daemon_pid != nlh->nlmsg_pid) {
+			*errp = -EBUSY;
+		}
+	} else
+		_dm_netlink_daemon_pid = nlh->nlmsg_pid;
+	spin_unlock(&_dm_netlink_pid_lock);
+
+out:
+	return *errp;
+}
+
+static void dm_netlink_rcv(struct sock *sk, int len)
+{
+	unsigned qlen = 0;
+
+	do
+		netlink_run_queue(sk, &qlen, &dm_netlink_rcv_msg);
+	while (qlen);
+
+}
+
+static int dm_netlink_rcv_event(struct notifier_block *this,
+				unsigned long event, void *ptr)
+{
+	struct netlink_notify *n = ptr;
+
+	spin_lock(&_dm_netlink_pid_lock);
+
+	if (event == NETLINK_URELEASE &&
+	    n->protocol == NETLINK_DM && n->pid &&
+	    n->pid == _dm_netlink_daemon_pid)
+		_dm_netlink_daemon_pid = 0;
+
+	spin_unlock(&_dm_netlink_pid_lock);
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block dm_netlink_notifier = {
+	.notifier_call  = dm_netlink_rcv_event,
+};
+
 int __init dm_netlink_init(void)
 {
 	int r;
 
+	r = netlink_register_notifier(&dm_netlink_notifier);
+	if (r)
+		return r;
+
+	_dm_netlink_sock = netlink_kernel_create(NETLINK_DM, 0,
+					   dm_netlink_rcv, THIS_MODULE);
+	if (!_dm_netlink_sock) {
+		r = -ENOBUFS;
+		goto notifier_out;
+	}
 	r = dme_cache_init(&_dme_cache, DM_EVENT_SKB_SIZE);
-	if (!r)
-		DMINFO("version 1.0.0 loaded");
+	if (r)
+		goto socket_out;
+
+	DMINFO("version 1.0.0 loaded");
+
+	return 0;
+
+socket_out:
+	sock_release(_dm_netlink_sock->sk_socket);
+notifier_out:
+	netlink_unregister_notifier(&dm_netlink_notifier);
+	DMERR("%s: dme_cache_init failed: %d", __FUNCTION__, r);
 
 	return r;
 }
@@ -100,4 +303,6 @@ int __init dm_netlink_init(void)
 void dm_netlink_exit(void)
 {
 	dme_cache_destroy(&_dme_cache);
+	sock_release(_dm_netlink_sock->sk_socket);
+	netlink_unregister_notifier(&dm_netlink_notifier);
 }
Index: linux-2.6.21-rc5/drivers/md/dm-netlink.h
===================================================================
--- linux-2.6.21-rc5.orig/drivers/md/dm-netlink.h	2007-04-04 19:28:59.000000000 +0100
+++ linux-2.6.21-rc5/drivers/md/dm-netlink.h	2007-04-04 19:29:01.000000000 +0100
@@ -21,19 +21,22 @@
 #ifndef DM_NETLINK_H
 #define DM_NETLINK_H
 
-struct dm_event_cache;
+#include <linux/dm-netlink.h>
+
+struct dm_table;
 struct mapped_device;
-struct dm_event {
-	struct dm_event_cache *cdata;
-	struct mapped_device *md;
-	struct sk_buff *skb;
-	struct list_head elist;
-};
+struct dm_event;
+
+void dm_event_add(struct mapped_device *md, struct list_head *elist);
 
 #ifdef CONFIG_DM_NETLINK
 
 int dm_netlink_init(void);
 void dm_netlink_exit(void);
+void dm_netlink_send_events(struct list_head *events);
+
+void dm_path_event(enum dm_netlink_event_type evt_type, struct dm_table *t,
+                   const char *path, int nr_valid_paths);
 
 #else	/* CONFIG_DM_NETLINK */
 
@@ -44,6 +47,14 @@ static inline int __init dm_netlink_init
 static inline void dm_netlink_exit(void)
 {
 }
+void inline dm_netlink_send_events(struct list_head *events)
+{
+}
+void inline dm_path_event(enum dm_netlink_event_type evt_type,
+			  struct dm_table *t, const char *path,
+			  int nr_valid_paths)
+{
+}
 
 #endif	/* CONFIG_DM_NETLINK */
 
Index: linux-2.6.21-rc5/drivers/md/dm.c
===================================================================
--- linux-2.6.21-rc5.orig/drivers/md/dm.c	2007-04-04 19:28:59.000000000 +0100
+++ linux-2.6.21-rc5/drivers/md/dm.c	2007-04-04 19:29:01.000000000 +0100
@@ -113,6 +113,8 @@ struct mapped_device {
 	 */
 	atomic_t event_nr;
 	wait_queue_head_t eventq;
+	struct list_head event_list;
+	spinlock_t event_lock;
 
 	/*
 	 * freeze/thaw support require holding onto a super block
@@ -1032,6 +1034,8 @@ static struct mapped_device *alloc_dev(i
 	atomic_set(&md->holders, 1);
 	atomic_set(&md->open_count, 0);
 	atomic_set(&md->event_nr, 0);
+	INIT_LIST_HEAD(&md->event_list);
+	spin_lock_init(&md->event_lock);
 
 	md->queue = blk_alloc_queue(GFP_KERNEL);
 	if (!md->queue)
@@ -1131,6 +1135,14 @@ static void free_dev(struct mapped_devic
 static void event_callback(void *context)
 {
 	struct mapped_device *md = (struct mapped_device *) context;
+	unsigned long flags;
+	LIST_HEAD(events);
+
+	spin_lock_irqsave(&md->event_lock, flags);
+	list_splice_init(&md->event_list, &events);
+	spin_unlock_irqrestore(&md->event_lock, flags);
+
+	dm_netlink_send_events(&events);
 
 	atomic_inc(&md->event_nr);
 	wake_up(&md->eventq);
@@ -1559,6 +1571,15 @@ int dm_wait_event(struct mapped_device *
 			(event_nr != atomic_read(&md->event_nr)));
 }
 
+void dm_event_add(struct mapped_device *md, struct list_head *elist)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&md->event_lock, flags);
+	list_add(elist, &md->event_list);
+	spin_unlock_irqrestore(&md->event_lock, flags);
+}
+
 /*
  * The gendisk is only valid as long as you have a reference
  * count on 'md'.
Index: linux-2.6.21-rc5/include/linux/Kbuild
===================================================================
--- linux-2.6.21-rc5.orig/include/linux/Kbuild	2007-04-04 19:22:50.000000000 +0100
+++ linux-2.6.21-rc5/include/linux/Kbuild	2007-04-04 19:29:01.000000000 +0100
@@ -185,6 +185,7 @@ unifdef-y += cyclades.h
 unifdef-y += dccp.h
 unifdef-y += dirent.h
 unifdef-y += dlm.h
+unifdef-y += dm-netlink.h
 unifdef-y += elfcore.h
 unifdef-y += errno.h
 unifdef-y += errqueue.h
Index: linux-2.6.21-rc5/include/linux/dm-netlink.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.21-rc5/include/linux/dm-netlink.h	2007-04-04 19:29:01.000000000 +0100
@@ -0,0 +1,59 @@
+/*
+ * Device Mapper Netlink Support
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Copyright IBM Corporation, 2005, 2006
+ * 	Author: Mike Anderson <[email-masked]>
+ */
+#ifndef LINUX_DM_NETLINK_H
+#define LINUX_DM_NETLINK_H
+
+#include <linux/netlink.h>
+
+#ifdef CONFIG_DM_NETLINK
+
+/*
+ * Single netlink message type used for all DM events
+ */
+#define DM_EVENT_MSG NLMSG_MIN_TYPE + 1
+
+enum dm_netlink_event_type {
+	DM_EVENT_PATH_FAILED = 1,
+	DM_EVENT_PATH_REINSTATED = 2,
+	DM_EVENT_MAX,
+};
+
+// FIXME Explain all these!
+enum dm_netlink_event_attr {
+	DM_EVENT_ATTR_SEQNUM		= 1,	/* Sequence number */
+	DM_EVENT_ATTR_TSSEC		= 2,
+	DM_EVENT_ATTR_TSUSEC		= 3,
+	DM_EVENT_ATTR_NAME		= 4,	/* Major:Minor */
+	DM_EVENT_ATTR_PATH		= 5,	/* (Multipath) Pathname */
+	DM_EVENT_ATTR_NUM_VALID_PATHS	= 6,	/* (Multipath) */
+	DM_EVENT_ATTR_MAX,
+};
+
+struct dm_netlink_msghdr {
+	uint16_t type;
+	uint16_t version;
+	uint16_t reserved1;
+	uint16_t reserved2;
+} __attribute__((aligned(sizeof(uint64_t))));
+
+#endif	/* CONFIG_DM_NETLINK */
+
+#endif	/* LINUX_DM_NETLINK_H */