diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index 3bc28f08aad1..fc75dc53a218 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -33,7 +33,22 @@ static bool should_merge(struct fsnotify_event *old_fsn,
 	if (fanotify_event_has_path(old)) {
 		return old->path.mnt == new->path.mnt &&
 			old->path.dentry == new->path.dentry;
-	} else if (fanotify_event_has_fid(old)) {
+	}
+
+	if (!fanotify_fsid_equal(&old->fsid, &new->fsid))
+		return false;
+
+	if (fanotify_event_has_dfid_name(old)) {
+		if (!fanotify_dfid_name_equal(FANOTIFY_NE(old_fsn),
+					      FANOTIFY_NE(new_fsn)))
+			return false;
+
+		/* FAN_DIR_MODIFY does not encode the "child" fid */
+		if (!fanotify_event_has_fid(old))
+			return true;
+	}
+
+	if (fanotify_event_has_fid(old)) {
 		/*
 		 * We want to merge many dirent events in the same dir (i.e.
 		 * creates/unlinks/renames), but we do not want to merge dirent
@@ -43,7 +58,6 @@ static bool should_merge(struct fsnotify_event *old_fsn,
 		 * unlink pair or rmdir+create pair of events.
 		 */
 		return (old->mask & FS_ISDIR) == (new->mask & FS_ISDIR) &&
-			fanotify_fsid_equal(&old->fsid, &new->fsid) &&
 			fanotify_fid_equal(&old->fid, &new->fid, old->fh.len);
 	}
 
@@ -279,13 +293,16 @@ static struct inode *fanotify_fid_inode(struct inode *to_tell, u32 event_mask,
 struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
 					    struct inode *inode, u32 mask,
 					    const void *data, int data_type,
+					    const struct qstr *file_name,
 					    __kernel_fsid_t *fsid)
 {
 	struct fanotify_event *event = NULL;
+	struct fanotify_name_event *fne = NULL;
 	gfp_t gfp = GFP_KERNEL_ACCOUNT;
 	struct inode *id = fanotify_fid_inode(inode, mask, data, data_type);
 	const struct path *path = fsnotify_data_path(data, data_type);
 	struct dentry *dentry = fsnotify_data_dentry(data, data_type);
+	struct inode *dir = NULL;
 
 	/*
 	 * For queues with unlimited length lost events are not expected and
@@ -310,12 +327,56 @@ struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
 		event = &pevent->fae;
 		pevent->response = 0;
 		pevent->state = FAN_EVENT_INIT;
+		/*
+		 * Make sure that fanotify_event_has_fid() and
+		 * fanotify_event_has_name() are false for permission events.
+		 */
+		id = NULL;
+		event->dfh.type = FILEID_ROOT;
+		goto init;
+	}
+
+	/*
+	 * For FAN_DIR_MODIFY event, we report the fid of the directory and
+	 * the name of the modified entry.
+	 * Allocate an fanotify_name_event struct and copy the name.
+	 */
+	if (mask & FAN_DIR_MODIFY && !(WARN_ON_ONCE(!file_name))) {
+		char *name = NULL;
+
+		/*
+		 * Make sure that fanotify_event_has_name() is true and that
+		 * fanotify_event_has_fid() is false for FAN_DIR_MODIFY events.
+		 */
+		id = NULL;
+		dir = inode;
+		if (file_name->len + 1 > FANOTIFY_INLINE_NAME_LEN) {
+			name = kmalloc(file_name->len + 1, gfp);
+			if (!name)
+				goto out;
+		}
+
+		fne = kmem_cache_alloc(fanotify_name_event_cachep, gfp);
+		if (!fne)
+			goto out;
+
+		event = &fne->fae;
+		if (!name)
+			name = fne->inline_name;
+		strcpy(name, file_name->name);
+		fne->name.name = name;
+		fne->name.len = file_name->len;
+		event->fh.type = FILEID_INVALID;
+		event->dfh.type = FILEID_INVALID;
 		goto init;
 	}
+
 	event = kmem_cache_alloc(fanotify_event_cachep, gfp);
 	if (!event)
 		goto out;
-init: __maybe_unused
+
+	event->dfh.type = FILEID_ROOT;
+init:
 	/*
 	 * Use the dentry instead of inode as tag for event queue, so event
 	 * reported on parent is merged with event reported on child when both
@@ -328,11 +389,16 @@ init: __maybe_unused
 	else
 		event->pid = get_pid(task_tgid(current));
 	event->fh.len = 0;
+	event->dfh.len = 0;
 	if (fsid)
 		event->fsid = *fsid;
-	if (id && FAN_GROUP_FLAG(group, FAN_REPORT_FID)) {
+	if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) {
 		/* Report the event without a file identifier on encode error */
-		event->fh_type = fanotify_encode_fid(event, id, gfp, fsid);
+		if (id)
+			event->fh = fanotify_encode_fid(&event->fid, id, gfp);
+		/* The reported name is relative to 'dir' */
+		if (fne)
+			event->dfh = fanotify_encode_fid(&fne->dfid, dir, gfp);
 	} else if (path) {
 		event->fh.type = FILEID_ROOT;
 		event->path = *path;
@@ -439,7 +505,7 @@ static int fanotify_handle_event(struct fsnotify_group *group,
 	}
 
 	event = fanotify_alloc_event(group, inode, mask, data, data_type,
-				     &fsid);
+				     file_name, &fsid);
 	ret = -ENOMEM;
 	if (unlikely(!event)) {
 		/*
@@ -494,6 +560,15 @@ static void fanotify_free_event(struct fsnotify_event *fsn_event)
 		kmem_cache_free(fanotify_perm_event_cachep,
 				FANOTIFY_PE(fsn_event));
 		return;
+	} else if (fanotify_event_has_dfid_name(event)) {
+		struct fanotify_name_event *fne = FANOTIFY_NE(fsn_event);
+
+		if (fanotify_fid_has_ext_fh(&event->dfh))
+			kfree(fne->dfid.ext_fh);
+		if (fanotify_event_has_ext_name(fne))
+			kfree(fne->name.name);
+		kmem_cache_free(fanotify_name_event_cachep, fne);
+		return;
 	}
 	kmem_cache_free(fanotify_event_cachep, event);
 }
diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h
index 4fee002235b6..e4a67a2d77b8 100644
--- a/fs/notify/fanotify/fanotify.h
+++ b/fs/notify/fanotify/fanotify.h
@@ -6,6 +6,7 @@
 
 extern struct kmem_cache *fanotify_mark_cache;
 extern struct kmem_cache *fanotify_event_cachep;
+extern struct kmem_cache *fanotify_name_event_cachep;
 extern struct kmem_cache *fanotify_perm_event_cachep;
 
 /* Possible states of the permission event */
@@ -84,9 +85,10 @@ struct fanotify_event {
 	 * on 64bit arch and to use fh.type as an indication of whether path
 	 * or fid are used in the union:
 	 * FILEID_ROOT (0) for path, > 0 for fid, FILEID_INVALID for neither.
+	 * Non zero dfh.type indicates embedded in an fanotify_name_event.
 	 */
 	struct fanotify_fid_hdr fh;
-	u16 pad;
+	struct fanotify_fid_hdr dfh;
 	__kernel_fsid_t fsid;
 	union {
 		/*
@@ -114,6 +116,66 @@ static inline bool fanotify_event_has_fid(struct fanotify_event *event)
 	return fanotify_fid_has_fh(&event->fh);
 }
 
+/*
+ * Structure for fanotify events with name info.
+ * DNAME_INLINE_LEN is good enough for dentry name, so it's good enough for us.
+ * It also happens to bring the size of this struct to 128 bytes on 64bit arch.
+ */
+#define FANOTIFY_INLINE_NAME_LEN DNAME_INLINE_LEN
+
+struct fanotify_name_event {
+	struct fanotify_event fae;
+	struct fanotify_fid  dfid;
+	struct qstr name;
+	unsigned char inline_name[FANOTIFY_INLINE_NAME_LEN];
+};
+
+static inline struct fanotify_name_event *
+FANOTIFY_NE(struct fsnotify_event *fse)
+{
+	return container_of(fse, struct fanotify_name_event, fae.fse);
+}
+
+static inline bool fanotify_event_has_dfid_name(struct fanotify_event *event)
+{
+	return event->dfh.type != FILEID_ROOT;
+}
+
+static inline unsigned int fanotify_event_name_len(struct fanotify_event *event)
+{
+	return event->dfh.type != FILEID_ROOT ?
+		FANOTIFY_NE(&event->fse)->name.len : 0;
+}
+
+static inline bool fanotify_event_has_ext_name(struct fanotify_name_event *fne)
+{
+	return fne->name.len + 1 > FANOTIFY_INLINE_NAME_LEN;
+}
+
+static inline bool fanotify_dfid_name_equal(struct fanotify_name_event *fne1,
+					    struct fanotify_name_event *fne2)
+{
+	struct qstr *name1 = &fne1->name;
+	struct qstr *name2 = &fne2->name;
+	struct fanotify_fid_hdr *dfh1 = &fne1->fae.dfh;
+	struct fanotify_fid_hdr *dfh2 = &fne2->fae.dfh;
+
+	if (dfh1->type != dfh2->type || dfh1->len != dfh2->len ||
+	    name1->len != name2->len)
+		return false;
+
+	/* Could be pointing to same external_name */
+	if (name1->len && name1->name != name2->name &&
+	    strcmp(name1->name, name2->name))
+		return false;
+
+	/* No dfid means that encoding failed */
+	if (!dfh1->len)
+		return true;
+
+	return fanotify_fid_equal(&fne1->dfid, &fne2->dfid, dfh1->len);
+}
+
 /*
  * Structure for permission fanotify events. It gets allocated and freed in
  * fanotify_handle_event() since we wait there for user response. When the
@@ -148,4 +210,5 @@ static inline struct fanotify_event *FANOTIFY_E(struct fsnotify_event *fse)
 struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
 					    struct inode *inode, u32 mask,
 					    const void *data, int data_type,
+					    const struct qstr *file_name,
 					    __kernel_fsid_t *fsid);
diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c
index beb9f0661a7c..284f3548bb79 100644
--- a/fs/notify/fanotify/fanotify_user.c
+++ b/fs/notify/fanotify/fanotify_user.c
@@ -47,6 +47,7 @@ extern const struct fsnotify_ops fanotify_fsnotify_ops;
 
 struct kmem_cache *fanotify_mark_cache __read_mostly;
 struct kmem_cache *fanotify_event_cachep __read_mostly;
+struct kmem_cache *fanotify_name_event_cachep __read_mostly;
 struct kmem_cache *fanotify_perm_event_cachep __read_mostly;
 
 #define FANOTIFY_EVENT_ALIGN 4
@@ -831,7 +832,7 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
 	group->memcg = get_mem_cgroup_from_mm(current->mm);
 
 	oevent = fanotify_alloc_event(group, NULL, FS_Q_OVERFLOW, NULL,
-				      FSNOTIFY_EVENT_NONE, NULL);
+				      FSNOTIFY_EVENT_NONE, NULL, NULL);
 	if (unlikely(!oevent)) {
 		fd = -ENOMEM;
 		goto out_destroy_group;
@@ -1147,6 +1148,8 @@ static int __init fanotify_user_setup(void)
 	fanotify_mark_cache = KMEM_CACHE(fsnotify_mark,
 					 SLAB_PANIC|SLAB_ACCOUNT);
 	fanotify_event_cachep = KMEM_CACHE(fanotify_event, SLAB_PANIC);
+	fanotify_name_event_cachep = KMEM_CACHE(fanotify_name_event,
+						SLAB_PANIC);
 	if (IS_ENABLED(CONFIG_FANOTIFY_ACCESS_PERMISSIONS)) {
 		fanotify_perm_event_cachep =
 			KMEM_CACHE(fanotify_perm_event, SLAB_PANIC);
