From: Geremy Condra <[email-masked]>

With this change dm-verity errors will cause uevents to be
sent to userspace, notifying it that an error has occurred
and potentially triggering recovery actions.

Signed-off-by: Geremy Condra <[email-masked]>
Acked-by: Will Drewry <[email-masked]>

FIXME AGK - Mid-edit 

---
 drivers/md/dm-uevent.c |  116 ++++++++++++++++++++++++++++++++++++++++---------
 drivers/md/dm-uevent.h |   10 +++-
 drivers/md/dm-verity.c |   48 ++++++++++++++++++++
 3 files changed, 151 insertions(+), 23 deletions(-)

Index: linux/drivers/md/dm-uevent.c
===================================================================
--- linux.orig/drivers/md/dm-uevent.c
+++ linux/drivers/md/dm-uevent.c
@@ -18,15 +18,14 @@
  * Copyright IBM Corporation, 2007
  * 	Author: Mike Anderson <[email-masked]>
  */
-#include <linux/list.h>
+#include "dm.h"
+#include "dm-uevent.h"
+
 #include <linux/slab.h>
 #include <linux/kobject.h>
 #include <linux/dm-ioctl.h>
 #include <linux/export.h>
 
-#include "dm.h"
-#include "dm-uevent.h"
-
 #define DM_MSG_PREFIX "uevent"
 
 static const struct {
@@ -36,6 +35,8 @@ static const struct {
 } _dm_uevent_type_names[] = {
 	{DM_UEVENT_PATH_FAILED, KOBJ_CHANGE, "PATH_FAILED"},
 	{DM_UEVENT_PATH_REINSTATED, KOBJ_CHANGE, "PATH_REINSTATED"},
+	{DM_UEVENT_DATA_ERROR, KOBJ_CHANGE, "DATA_ERROR"},
+	{DM_UEVENT_HASH_ERROR, KOBJ_CHANGE, "HASH_ERROR"},
 };
 
 static struct kmem_cache *_dm_event_cache;
@@ -68,39 +69,51 @@ static struct dm_uevent *dm_uevent_alloc
 	return event;
 }
 
-static struct dm_uevent *dm_build_path_uevent(struct mapped_device *md,
-					      struct dm_target *ti,
-					      enum kobject_action action,
-					      const char *dm_action,
-					      const char *path,
-					      unsigned nr_valid_paths)
+static int uevent_init(struct dm_uevent *event, struct mapped_device *md,
+		       struct dm_target *ti, enum kobject_action action,
+		       const char *dm_action)
 {
-	struct dm_uevent *event;
-
-	event = dm_uevent_alloc(md);
-	if (!event) {
-		DMERR("%s: dm_uevent_alloc() failed", __func__);
-		goto err_nomem;
-	}
-
 	event->action = action;
 
 	if (add_uevent_var(&event->ku_env, "DM_TARGET=%s", ti->type->name)) {
 		DMERR("%s: add_uevent_var() for DM_TARGET failed",
 		      __func__);
-		goto err_add;
+		return -ENOMEM;
 	}
 
 	if (add_uevent_var(&event->ku_env, "DM_ACTION=%s", dm_action)) {
 		DMERR("%s: add_uevent_var() for DM_ACTION failed",
 		      __func__);
-		goto err_add;
+		return -ENOMEM;
 	}
 
 	if (add_uevent_var(&event->ku_env, "DM_SEQNUM=%u",
 			   dm_next_uevent_seq(md))) {
 		DMERR("%s: add_uevent_var() for DM_SEQNUM failed",
 		      __func__);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static struct dm_uevent *dm_build_path_uevent(struct mapped_device *md,
+					      struct dm_target *ti,
+					      enum kobject_action action,
+					      const char *dm_action,
+					      const char *path,
+					      unsigned nr_valid_paths)
+{
+	struct dm_uevent *event;
+
+	event = dm_uevent_alloc(md);
+	if (!event) {
+		DMERR("%s: dm_uevent_alloc() failed", __func__);
+		goto err_nomem;
+	}
+
+	if (!uevent_init(event, md, ti, action, dm_action)) {
+		DMERR("%s: uevent_init() failed", __func__);
 		goto err_add;
 	}
 
@@ -124,6 +137,36 @@ err_nomem:
 	return ERR_PTR(-ENOMEM);
 }
 
+static struct dm_uevent *dm_build_verity_uevent(struct mapped_device *md,
+						struct dm_target *ti,
+						enum kobject_action action,
+						const char *dm_action,
+						sector_t block_nr)
+{
+	struct dm_uevent *event;
+
+	event = dm_uevent_alloc(md);
+	if (!event) {
+		DMERR("%s: dm_uevent_alloc() failed", __func__);
+		goto err_nomem;
+	}
+
+	if (!uevent_init(event, md, ti, action, dm_action)) {
+		DMERR("%s: uevent_init() failed", __func__);
+		goto err_add;
+	}
+
+	if (add_uevent_var(&event->ku_env, "DM_VERITY_BLOCK_NR=%llu", (unsigned long long)block_nr)) {
+		DMERR("%s: add_uevent_var() for DM_VERITY_BLOCK failed", __func__);
+		goto err_add;
+	}
+
+err_add:
+	dm_uevent_free(event);
+err_nomem:
+	return ERR_PTR(-ENOMEM);
+}
+
 /**
  * dm_send_uevents - send uevents for given list
  *
@@ -202,13 +245,44 @@ void dm_path_uevent(enum dm_uevent_type 
 }
 EXPORT_SYMBOL_GPL(dm_path_uevent);
 
+/**
+ * dm_verity_uevent - called to create a new verity event and queue it
+ *
+ * @event_type:	verity event type enum
+ * @ti:		pointer to a dm_target
+ * @block_nr:	block number triggering the event
+ *
+ */
+void dm_verity_uevent(enum dm_uevent_type event_type, struct dm_target *ti,
+		      sector_t block_nr)
+{
+	struct mapped_device *md = dm_table_get_md(ti->table);
+	struct dm_uevent *event;
+
+	if (event_type >= ARRAY_SIZE(_dm_uevent_type_names)) {
+		DMERR("%s: Invalid event_type %d", __func__, event_type);
+		return;
+	}
+
+	event = dm_build_verity_uevent(md, ti,
+				       _dm_uevent_type_names[event_type].action,
+				       _dm_uevent_type_names[event_type].name,
+				       block_nr);
+
+	if (IS_ERR(event))
+		return;
+
+	dm_uevent_add(md, &event->elist);
+}
+EXPORT_SYMBOL_GPL(dm_verity_uevent);
+
 int dm_uevent_init(void)
 {
 	_dm_event_cache = KMEM_CACHE(dm_uevent, 0);
 	if (!_dm_event_cache)
 		return -ENOMEM;
 
-	DMINFO("version 1.0.3");
+	DMINFO("version 1.1.0");
 
 	return 0;
 }
Index: linux/drivers/md/dm-uevent.h
===================================================================
--- linux.orig/drivers/md/dm-uevent.h
+++ linux/drivers/md/dm-uevent.h
@@ -24,6 +24,8 @@
 enum dm_uevent_type {
 	DM_UEVENT_PATH_FAILED,
 	DM_UEVENT_PATH_REINSTATED,
+	DM_UEVENT_DATA_ERROR,
+	DM_UEVENT_HASH_ERROR,
 };
 
 #ifdef CONFIG_DM_UEVENT
@@ -34,7 +36,8 @@ extern void dm_send_uevents(struct list_
 extern void dm_path_uevent(enum dm_uevent_type event_type,
 			   struct dm_target *ti, const char *path,
 			   unsigned nr_valid_paths);
-
+extern void dm_verity_uevent(enum dm_uevent_type event_type,
+			     struct dm_target *ti, sector_t block_nr);
 #else
 
 static inline int dm_uevent_init(void)
@@ -53,7 +56,10 @@ static inline void dm_path_uevent(enum d
 				  unsigned nr_valid_paths)
 {
 }
-
+static inline void dm_verity_uevent(enum dm_uevent_type event_type,
+				    struct dm_target *ti, sector_t block_nr)
+{
+}
 #endif	/* CONFIG_DM_UEVENT */
 
 #endif	/* DM_UEVENT_H */
Index: linux/drivers/md/dm-verity.c
===================================================================
--- linux.orig/drivers/md/dm-verity.c
+++ linux/drivers/md/dm-verity.c
@@ -20,6 +20,8 @@
 #include <linux/device-mapper.h>
 #include <crypto/hash.h>
 
+#include "dm-uevent.h"
+
 #define DM_MSG_PREFIX			"verity"
 
 #define DM_VERITY_IO_VEC_INLINE		16
@@ -58,6 +60,7 @@ struct dm_verity {
 	mempool_t *vec_mempool;	/* mempool of bio vector */
 
 	struct workqueue_struct *verify_wq;
+	struct work_struct trigger_event;
 
 	/* starting blocks for each tree level. 0 is the lowest level. */
 	sector_t hash_level_block[DM_VERITY_MAX_LEVELS];
@@ -142,6 +145,44 @@ static void dm_bufio_alloc_callback(stru
 }
 
 /*
+ * Trigger userspace data corruption handler.
+ */
+static void trigger_event(struct work_struct *work)
+{
+	struct dm_verity *v = container_of(work, struct dm_verity, trigger_event);
+
+	dm_table_event(v->ti->table);
+}
+
+static void verity_postsuspend(struct dm_target *ti)
+{
+	struct dm_verity *v = ti->private;
+
+	flush_work(&v->trigger_event);
+}
+
+#ifdef CONFIG_DM_UEVENT
+static void verity_data_error(struct dm_verity *v, unsigned long long block_nr)
+{
+	dm_verity_uevent(DM_UEVENT_DATA_ERROR, v->ti, block_nr);
+	schedule_work(&v->trigger_event);
+}
+
+static void verity_hash_error(struct dm_verity *v, unsigned long long block_nr)
+{
+	dm_verity_uevent(DM_UEVENT_HASH_ERROR, v->ti, block_nr);
+	schedule_work(&v->trigger_event);
+}
+#else
+static inline void verity_data_error(struct dm_verity *v, unsigned long long block_nr)
+{
+}
+static inline void verity_hash_error(struct dm_verity *v, unsigned long long block_nr)
+{
+}
+#endif
+
+/*
  * Translate input sector number to the sector number on the target device.
  */
 static sector_t verity_map_sector(struct dm_verity *v, sector_t bi_sector)
@@ -260,6 +301,7 @@ static int verity_verify_level(struct dm
 				(unsigned long long)hash_block);
 			v->hash_failed = 1;
 			r = -EIO;
+			verity_hash_error(v, (unsigned long long)hash_block);
 			goto release_ret_r;
 		} else
 			aux->hash_verified = 1;
@@ -380,6 +422,7 @@ test_block_hash:
 			DMERR_LIMIT("data block %llu is corrupted",
 				(unsigned long long)(io->block + b));
 			v->hash_failed = 1;
+			verity_data_error(v, (unsigned long long)(io->block + b));
 			return -EIO;
 		}
 	}
@@ -625,6 +668,8 @@ static void verity_dtr(struct dm_target 
 {
 	struct dm_verity *v = ti->private;
 
+	flush_work(&v->trigger_event);
+
 	if (v->verify_wq)
 		destroy_workqueue(v->verify_wq);
 
@@ -876,6 +921,8 @@ static int verity_ctr(struct dm_target *
 		goto bad;
 	}
 
+	INIT_WORK(&v->trigger_event, trigger_event);
+
 	return 0;
 
 bad:
@@ -894,6 +941,7 @@ static struct target_type verity_target 
 	.status		= verity_status,
 	.ioctl		= verity_ioctl,
 	.merge		= verity_merge,
+	.postsuspend	= verity_postsuspend,
 	.iterate_devices = verity_iterate_devices,
 	.io_hints	= verity_io_hints,
 };