diff options
Diffstat (limited to 'fs/notify/fsnotify.c')
-rw-r--r-- | fs/notify/fsnotify.c | 134 |
1 files changed, 84 insertions, 50 deletions
diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 0bb4aeb8e00f..cdaa51cb698c 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c | |||
@@ -140,19 +140,31 @@ void __fsnotify_parent(struct file *file, struct dentry *dentry, __u32 mask) | |||
140 | } | 140 | } |
141 | EXPORT_SYMBOL_GPL(__fsnotify_parent); | 141 | EXPORT_SYMBOL_GPL(__fsnotify_parent); |
142 | 142 | ||
143 | static int send_to_group(struct fsnotify_group *group, struct inode *to_tell, | 143 | static int send_to_group(struct inode *to_tell, struct vfsmount *mnt, |
144 | struct vfsmount *mnt, struct fsnotify_mark *mark, | 144 | struct fsnotify_mark *mark, |
145 | __u32 mask, void *data, int data_is, u32 cookie, | 145 | __u32 mask, void *data, |
146 | int data_is, u32 cookie, | ||
146 | const unsigned char *file_name, | 147 | const unsigned char *file_name, |
147 | struct fsnotify_event **event) | 148 | struct fsnotify_event **event) |
148 | { | 149 | { |
150 | struct fsnotify_group *group = mark->group; | ||
151 | __u32 test_mask = (mask & ~FS_EVENT_ON_CHILD); | ||
152 | |||
149 | pr_debug("%s: group=%p to_tell=%p mnt=%p mark=%p mask=%x data=%p" | 153 | pr_debug("%s: group=%p to_tell=%p mnt=%p mark=%p mask=%x data=%p" |
150 | " data_is=%d cookie=%d event=%p\n", __func__, group, to_tell, | 154 | " data_is=%d cookie=%d event=%p\n", __func__, group, to_tell, |
151 | mnt, mark, mask, data, data_is, cookie, *event); | 155 | mnt, mark, mask, data, data_is, cookie, *event); |
152 | 156 | ||
157 | if ((mask & FS_MODIFY) && | ||
158 | !(mark->flags & FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY)) | ||
159 | mark->ignored_mask = 0; | ||
160 | |||
161 | if (!(test_mask & mark->mask & ~mark->ignored_mask)) | ||
162 | return 0; | ||
163 | |||
153 | if (group->ops->should_send_event(group, to_tell, mnt, mark, mask, | 164 | if (group->ops->should_send_event(group, to_tell, mnt, mark, mask, |
154 | data, data_is) == false) | 165 | data, data_is) == false) |
155 | return 0; | 166 | return 0; |
167 | |||
156 | if (!*event) { | 168 | if (!*event) { |
157 | *event = fsnotify_create_event(to_tell, mask, data, | 169 | *event = fsnotify_create_event(to_tell, mask, data, |
158 | data_is, file_name, | 170 | data_is, file_name, |
@@ -172,67 +184,89 @@ static int send_to_group(struct fsnotify_group *group, struct inode *to_tell, | |||
172 | int fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is, | 184 | int fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is, |
173 | const unsigned char *file_name, u32 cookie) | 185 | const unsigned char *file_name, u32 cookie) |
174 | { | 186 | { |
175 | struct fsnotify_mark *mark; | 187 | struct hlist_node *inode_node, *vfsmount_node; |
176 | struct fsnotify_group *group; | 188 | struct fsnotify_mark *inode_mark = NULL, *vfsmount_mark = NULL; |
189 | struct fsnotify_group *inode_group, *vfsmount_group; | ||
177 | struct fsnotify_event *event = NULL; | 190 | struct fsnotify_event *event = NULL; |
178 | struct hlist_node *node; | 191 | struct vfsmount *mnt; |
179 | struct vfsmount *mnt = NULL; | ||
180 | int idx, ret = 0; | 192 | int idx, ret = 0; |
193 | bool used_inode = false, used_vfsmount = false; | ||
181 | /* global tests shouldn't care about events on child only the specific event */ | 194 | /* global tests shouldn't care about events on child only the specific event */ |
182 | __u32 test_mask = (mask & ~FS_EVENT_ON_CHILD); | 195 | __u32 test_mask = (mask & ~FS_EVENT_ON_CHILD); |
183 | 196 | ||
184 | if (data_is == FSNOTIFY_EVENT_FILE) | 197 | if (data_is == FSNOTIFY_EVENT_FILE) |
185 | mnt = ((struct file *)data)->f_path.mnt; | 198 | mnt = ((struct file *)data)->f_path.mnt; |
199 | else | ||
200 | mnt = NULL; | ||
201 | |||
202 | /* | ||
203 | * if this is a modify event we may need to clear the ignored masks | ||
204 | * otherwise return if neither the inode nor the vfsmount care about | ||
205 | * this type of event. | ||
206 | */ | ||
207 | if (!(mask & FS_MODIFY) && | ||
208 | !(test_mask & to_tell->i_fsnotify_mask) && | ||
209 | !(mnt && test_mask & mnt->mnt_fsnotify_mask)) | ||
210 | return 0; | ||
186 | 211 | ||
187 | idx = srcu_read_lock(&fsnotify_mark_srcu); | 212 | idx = srcu_read_lock(&fsnotify_mark_srcu); |
188 | 213 | ||
189 | if ((test_mask & to_tell->i_fsnotify_mask) || (mask & FS_MODIFY)) { | 214 | if ((mask & FS_MODIFY) || |
190 | hlist_for_each_entry_rcu(mark, node, &to_tell->i_fsnotify_marks, i.i_list) { | 215 | (test_mask & to_tell->i_fsnotify_mask)) |
191 | 216 | inode_node = to_tell->i_fsnotify_marks.first; | |
192 | pr_debug("%s: inode_loop: mark=%p mark->mask=%x mark->ignored_mask=%x\n", | 217 | else |
193 | __func__, mark, mark->mask, mark->ignored_mask); | 218 | inode_node = NULL; |
194 | 219 | ||
195 | if ((mask & FS_MODIFY) && | 220 | if (mnt) { |
196 | !(mark->flags & FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY)) | 221 | if ((mask & FS_MODIFY) || |
197 | mark->ignored_mask = 0; | 222 | (test_mask & mnt->mnt_fsnotify_mask)) |
198 | 223 | vfsmount_node = mnt->mnt_fsnotify_marks.first; | |
199 | if (test_mask & mark->mask & ~mark->ignored_mask) { | 224 | else |
200 | group = mark->group; | 225 | vfsmount_node = NULL; |
201 | if (!group) | 226 | } else { |
202 | continue; | 227 | mnt = NULL; |
203 | ret = send_to_group(group, to_tell, NULL, mark, mask, | 228 | vfsmount_node = NULL; |
204 | data, data_is, cookie, file_name, | ||
205 | &event); | ||
206 | if (ret) | ||
207 | goto out; | ||
208 | } | ||
209 | } | ||
210 | } | 229 | } |
211 | 230 | ||
212 | if (mnt && ((test_mask & mnt->mnt_fsnotify_mask) || | 231 | while (inode_node || vfsmount_node) { |
213 | (mask & FS_MODIFY))) { | 232 | if (inode_node) { |
214 | hlist_for_each_entry_rcu(mark, node, &mnt->mnt_fsnotify_marks, m.m_list) { | 233 | inode_mark = hlist_entry(srcu_dereference(inode_node, &fsnotify_mark_srcu), |
215 | 234 | struct fsnotify_mark, i.i_list); | |
216 | pr_debug("%s: mnt_loop: mark=%p mark->mask=%x mark->ignored_mask=%x\n", | 235 | inode_group = inode_mark->group; |
217 | __func__, mark, mark->mask, mark->ignored_mask); | 236 | } else |
218 | 237 | inode_group = (void *)-1; | |
219 | if ((mask & FS_MODIFY) && | 238 | |
220 | !(mark->flags & FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY)) | 239 | if (vfsmount_node) { |
221 | mark->ignored_mask = 0; | 240 | vfsmount_mark = hlist_entry(srcu_dereference(vfsmount_node, &fsnotify_mark_srcu), |
222 | 241 | struct fsnotify_mark, m.m_list); | |
223 | if (test_mask & mark->mask & ~mark->ignored_mask) { | 242 | vfsmount_group = vfsmount_mark->group; |
224 | group = mark->group; | 243 | } else |
225 | if (!group) | 244 | vfsmount_group = (void *)-1; |
226 | continue; | 245 | |
227 | ret = send_to_group(group, to_tell, mnt, mark, mask, | 246 | if (inode_group < vfsmount_group) { |
228 | data, data_is, cookie, file_name, | 247 | /* handle inode */ |
229 | &event); | 248 | send_to_group(to_tell, NULL, inode_mark, mask, data, |
230 | if (ret) | 249 | data_is, cookie, file_name, &event); |
231 | goto out; | 250 | used_inode = true; |
232 | } | 251 | } else if (vfsmount_group < inode_group) { |
252 | send_to_group(to_tell, mnt, vfsmount_mark, mask, data, | ||
253 | data_is, cookie, file_name, &event); | ||
254 | used_vfsmount = true; | ||
255 | } else { | ||
256 | send_to_group(to_tell, mnt, vfsmount_mark, mask, data, | ||
257 | data_is, cookie, file_name, &event); | ||
258 | used_vfsmount = true; | ||
259 | send_to_group(to_tell, NULL, inode_mark, mask, data, | ||
260 | data_is, cookie, file_name, &event); | ||
261 | used_inode = true; | ||
233 | } | 262 | } |
263 | |||
264 | if (used_inode) | ||
265 | inode_node = inode_node->next; | ||
266 | if (used_vfsmount) | ||
267 | vfsmount_node = vfsmount_node->next; | ||
234 | } | 268 | } |
235 | out: | 269 | |
236 | srcu_read_unlock(&fsnotify_mark_srcu, idx); | 270 | srcu_read_unlock(&fsnotify_mark_srcu, idx); |
237 | /* | 271 | /* |
238 | * fsnotify_create_event() took a reference so the event can't be cleaned | 272 | * fsnotify_create_event() took a reference so the event can't be cleaned |