fanotify.c 3.9 KB
Newer Older
1 2 3 4 5 6 7 8
#include <linux/fdtable.h>
#include <linux/fsnotify_backend.h>
#include <linux/init.h>
#include <linux/kernel.h> /* UINT_MAX */
#include <linux/types.h>

#include "fanotify.h"

9 10 11 12
static bool should_merge(struct fsnotify_event *old, struct fsnotify_event *new)
{
	pr_debug("%s: old=%p new=%p\n", __func__, old, new);

13 14 15
	if (old->to_tell == new->to_tell &&
	    old->data_type == new->data_type &&
	    old->tgid == new->tgid) {
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
		switch (old->data_type) {
		case (FSNOTIFY_EVENT_PATH):
			if ((old->path.mnt == new->path.mnt) &&
			    (old->path.dentry == new->path.dentry))
				return true;
		case (FSNOTIFY_EVENT_NONE):
			return true;
		default:
			BUG();
		};
	}
	return false;
}

static int fanotify_merge(struct list_head *list, struct fsnotify_event *event)
{
32
	struct fsnotify_event_holder *test_holder;
33
	struct fsnotify_event *test_event;
34 35
	struct fsnotify_event *new_event;
	int ret = 0;
36 37 38 39 40

	pr_debug("%s: list=%p event=%p\n", __func__, list, event);

	/* and the list better be locked by something too! */

41 42 43 44 45 46 47 48 49
	list_for_each_entry_reverse(test_holder, list, event_list) {
		test_event = test_holder->event;
		if (should_merge(test_event, event)) {
			ret = -EEXIST;

			/* if they are exactly the same we are done */
			if (test_event->mask == event->mask)
				goto out;

50 51 52 53 54 55 56 57 58 59
			/*
			 * if the refcnt == 1 this is the only queue
			 * for this event and so we can update the mask
			 * in place.
			 */
			if (atomic_read(&test_event->refcnt) == 1) {
				test_event->mask |= event->mask;
				goto out;
			}

60 61 62 63 64 65
			/* can't allocate memory, merge was no possible */
			new_event = fsnotify_clone_event(test_event);
			if (unlikely(!new_event)) {
				ret = 0;
				goto out;
			}
66

67 68 69 70 71 72 73 74 75 76 77
			/* build new event and replace it on the list */
			new_event->mask = (test_event->mask | event->mask);
			fsnotify_replace_event(test_holder, new_event);
			/* match ref from fsnotify_clone_event() */
			fsnotify_put_event(new_event);

			break;
		}
	}
out:
	return ret;
78 79
}

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
static int fanotify_handle_event(struct fsnotify_group *group, struct fsnotify_event *event)
{
	int ret;


	BUILD_BUG_ON(FAN_ACCESS != FS_ACCESS);
	BUILD_BUG_ON(FAN_MODIFY != FS_MODIFY);
	BUILD_BUG_ON(FAN_CLOSE_NOWRITE != FS_CLOSE_NOWRITE);
	BUILD_BUG_ON(FAN_CLOSE_WRITE != FS_CLOSE_WRITE);
	BUILD_BUG_ON(FAN_OPEN != FS_OPEN);
	BUILD_BUG_ON(FAN_EVENT_ON_CHILD != FS_EVENT_ON_CHILD);
	BUILD_BUG_ON(FAN_Q_OVERFLOW != FS_Q_OVERFLOW);

	pr_debug("%s: group=%p event=%p\n", __func__, group, event);

95 96 97 98
	ret = fsnotify_add_notify_event(group, event, NULL, fanotify_merge);
	/* -EEXIST means this event was merged with another, not that it was an error */
	if (ret == -EEXIST)
		ret = 0;
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
	return ret;
}

static bool fanotify_should_send_event(struct fsnotify_group *group, struct inode *inode,
				       struct vfsmount *mnt, __u32 mask, void *data,
				       int data_type)
{
	struct fsnotify_mark *fsn_mark;
	bool send;

	pr_debug("%s: group=%p inode=%p mask=%x data=%p data_type=%d\n",
		 __func__, group, inode, mask, data, data_type);

	/* sorry, fanotify only gives a damn about files and dirs */
	if (!S_ISREG(inode->i_mode) &&
	    !S_ISDIR(inode->i_mode))
		return false;

	/* if we don't have enough info to send an event to userspace say no */
	if (data_type != FSNOTIFY_EVENT_PATH)
		return false;

121
	fsn_mark = fsnotify_find_inode_mark(group, inode);
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
	if (!fsn_mark)
		return false;

	/* if the event is for a child and this inode doesn't care about
	 * events on the child, don't send it! */
	if ((mask & FS_EVENT_ON_CHILD) &&
	    !(fsn_mark->mask & FS_EVENT_ON_CHILD)) {
		send = false;
	} else {
		/*
		 * We care about children, but do we care about this particular
		 * type of event?
		 */
		mask = (mask & ~FS_EVENT_ON_CHILD);
		send = (fsn_mark->mask & mask);
	}

	/* find took a reference */
	fsnotify_put_mark(fsn_mark);

	return send;
}

const struct fsnotify_ops fanotify_fsnotify_ops = {
	.handle_event = fanotify_handle_event,
	.should_send_event = fanotify_should_send_event,
	.free_group_priv = NULL,
	.free_event_priv = NULL,
	.freeing_mark = NULL,
};