]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blame - fs/notify/fanotify/fanotify.c
fsnotify: intoduce a notification merge argument
[mirror_ubuntu-zesty-kernel.git] / fs / notify / fanotify / fanotify.c
CommitLineData
33d3dfff 1#include <linux/fanotify.h>
ff0b16a9
EP
2#include <linux/fdtable.h>
3#include <linux/fsnotify_backend.h>
4#include <linux/init.h>
5#include <linux/kernel.h> /* UINT_MAX */
1c529063 6#include <linux/mount.h>
ff0b16a9
EP
7#include <linux/types.h>
8
767cd46c
EP
9static bool should_merge(struct fsnotify_event *old, struct fsnotify_event *new)
10{
11 pr_debug("%s: old=%p new=%p\n", __func__, old, new);
12
32c32632
AG
13 if (old->to_tell == new->to_tell &&
14 old->data_type == new->data_type &&
15 old->tgid == new->tgid) {
767cd46c
EP
16 switch (old->data_type) {
17 case (FSNOTIFY_EVENT_PATH):
18 if ((old->path.mnt == new->path.mnt) &&
19 (old->path.dentry == new->path.dentry))
20 return true;
21 case (FSNOTIFY_EVENT_NONE):
22 return true;
23 default:
24 BUG();
25 };
26 }
27 return false;
28}
29
6e5f77b3
EP
30static int fanotify_merge(struct list_head *list,
31 struct fsnotify_event *event,
32 void **arg)
767cd46c 33{
a12a7dd3 34 struct fsnotify_event_holder *test_holder;
767cd46c 35 struct fsnotify_event *test_event;
a12a7dd3
EP
36 struct fsnotify_event *new_event;
37 int ret = 0;
767cd46c
EP
38
39 pr_debug("%s: list=%p event=%p\n", __func__, list, event);
40
41 /* and the list better be locked by something too! */
42
a12a7dd3
EP
43 list_for_each_entry_reverse(test_holder, list, event_list) {
44 test_event = test_holder->event;
45 if (should_merge(test_event, event)) {
46 ret = -EEXIST;
47
48 /* if they are exactly the same we are done */
49 if (test_event->mask == event->mask)
50 goto out;
51
9dced01a
EP
52 /*
53 * if the refcnt == 1 this is the only queue
54 * for this event and so we can update the mask
55 * in place.
56 */
57 if (atomic_read(&test_event->refcnt) == 1) {
58 test_event->mask |= event->mask;
59 goto out;
60 }
61
a12a7dd3
EP
62 /* can't allocate memory, merge was no possible */
63 new_event = fsnotify_clone_event(test_event);
64 if (unlikely(!new_event)) {
65 ret = 0;
66 goto out;
67 }
767cd46c 68
a12a7dd3
EP
69 /* build new event and replace it on the list */
70 new_event->mask = (test_event->mask | event->mask);
71 fsnotify_replace_event(test_holder, new_event);
72 /* match ref from fsnotify_clone_event() */
73 fsnotify_put_event(new_event);
74
75 break;
76 }
77 }
78out:
79 return ret;
767cd46c
EP
80}
81
ff0b16a9
EP
82static int fanotify_handle_event(struct fsnotify_group *group, struct fsnotify_event *event)
83{
84 int ret;
85
86
87 BUILD_BUG_ON(FAN_ACCESS != FS_ACCESS);
88 BUILD_BUG_ON(FAN_MODIFY != FS_MODIFY);
89 BUILD_BUG_ON(FAN_CLOSE_NOWRITE != FS_CLOSE_NOWRITE);
90 BUILD_BUG_ON(FAN_CLOSE_WRITE != FS_CLOSE_WRITE);
91 BUILD_BUG_ON(FAN_OPEN != FS_OPEN);
92 BUILD_BUG_ON(FAN_EVENT_ON_CHILD != FS_EVENT_ON_CHILD);
93 BUILD_BUG_ON(FAN_Q_OVERFLOW != FS_Q_OVERFLOW);
94
95 pr_debug("%s: group=%p event=%p\n", __func__, group, event);
96
6e5f77b3 97 ret = fsnotify_add_notify_event(group, event, NULL, fanotify_merge, NULL);
767cd46c
EP
98 /* -EEXIST means this event was merged with another, not that it was an error */
99 if (ret == -EEXIST)
100 ret = 0;
ff0b16a9
EP
101 return ret;
102}
103
1c529063 104static bool should_send_vfsmount_event(struct fsnotify_group *group, struct vfsmount *mnt,
32a4df13 105 struct inode *inode, __u32 mask)
ff0b16a9 106{
32a4df13
EP
107 struct fsnotify_mark *mnt_mark;
108 struct fsnotify_mark *inode_mark;
ff0b16a9 109
1c529063
EP
110 pr_debug("%s: group=%p vfsmount=%p mask=%x\n",
111 __func__, group, mnt, mask);
ff0b16a9 112
32a4df13
EP
113 mnt_mark = fsnotify_find_vfsmount_mark(group, mnt);
114 if (!mnt_mark)
ff0b16a9
EP
115 return false;
116
32a4df13
EP
117 mask &= mnt_mark->mask;
118 mask &= ~mnt_mark->ignored_mask;
119
120 if (mask) {
121 inode_mark = fsnotify_find_inode_mark(group, inode);
122 if (inode_mark) {
123 mask &= ~inode_mark->ignored_mask;
124 fsnotify_put_mark(inode_mark);
125 }
126 }
1c529063
EP
127
128 /* find took a reference */
32a4df13 129 fsnotify_put_mark(mnt_mark);
1c529063 130
32a4df13 131 return mask;
1c529063
EP
132}
133
134static bool should_send_inode_event(struct fsnotify_group *group, struct inode *inode,
135 __u32 mask)
136{
137 struct fsnotify_mark *fsn_mark;
1c529063
EP
138
139 pr_debug("%s: group=%p inode=%p mask=%x\n",
140 __func__, group, inode, mask);
ff0b16a9 141
5444e298 142 fsn_mark = fsnotify_find_inode_mark(group, inode);
ff0b16a9
EP
143 if (!fsn_mark)
144 return false;
145
146 /* if the event is for a child and this inode doesn't care about
147 * events on the child, don't send it! */
148 if ((mask & FS_EVENT_ON_CHILD) &&
149 !(fsn_mark->mask & FS_EVENT_ON_CHILD)) {
32a4df13 150 mask = 0;
ff0b16a9
EP
151 } else {
152 /*
153 * We care about children, but do we care about this particular
154 * type of event?
155 */
32a4df13
EP
156 mask &= ~FS_EVENT_ON_CHILD;
157 mask &= fsn_mark->mask;
158 mask &= ~fsn_mark->ignored_mask;
ff0b16a9
EP
159 }
160
161 /* find took a reference */
162 fsnotify_put_mark(fsn_mark);
163
32a4df13 164 return mask;
ff0b16a9
EP
165}
166
1c529063
EP
167static bool fanotify_should_send_event(struct fsnotify_group *group, struct inode *to_tell,
168 struct vfsmount *mnt, __u32 mask, void *data,
169 int data_type)
170{
171 pr_debug("%s: group=%p to_tell=%p mnt=%p mask=%x data=%p data_type=%d\n",
172 __func__, group, to_tell, mnt, mask, data, data_type);
173
174 /* sorry, fanotify only gives a damn about files and dirs */
175 if (!S_ISREG(to_tell->i_mode) &&
176 !S_ISDIR(to_tell->i_mode))
177 return false;
178
179 /* if we don't have enough info to send an event to userspace say no */
180 if (data_type != FSNOTIFY_EVENT_PATH)
181 return false;
182
183 if (mnt)
32a4df13 184 return should_send_vfsmount_event(group, mnt, to_tell, mask);
1c529063
EP
185 else
186 return should_send_inode_event(group, to_tell, mask);
187}
188
ff0b16a9
EP
189const struct fsnotify_ops fanotify_fsnotify_ops = {
190 .handle_event = fanotify_handle_event,
191 .should_send_event = fanotify_should_send_event,
192 .free_group_priv = NULL,
193 .free_event_priv = NULL,
194 .freeing_mark = NULL,
195};