diff options
author | Jan Kara <jack@suse.cz> | 2017-02-01 02:19:43 -0500 |
---|---|---|
committer | Jan Kara <jack@suse.cz> | 2017-04-10 11:37:35 -0400 |
commit | 04662cab59fc3e8421fd7a0539d304d51d2750a4 (patch) | |
tree | bde9d5a97ffbe63ea7366a4aab6be9e2402a2827 /fs/notify | |
parent | 2629718dd26f89e064dcdec6c8e5b9713502e1f8 (diff) |
fsnotify: Lock object list with connector lock
So far list of marks attached to an object (inode / vfsmount) was
protected by i_lock or mnt_root->d_lock. This dictates that the list
must be empty before the object can be destroyed although the list is
now anchored in the fsnotify_mark_connector structure. Protect the list
by a spinlock in the fsnotify_mark_connector structure to decouple
lifetime of a list of marks from a lifetime of the object. This also
simplifies the code quite a bit since we don't have to differentiate
between inode and vfsmount lists in quite a few places anymore.
Reviewed-by: Miklos Szeredi <mszeredi@redhat.com>
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Jan Kara <jack@suse.cz>
Diffstat (limited to 'fs/notify')
-rw-r--r-- | fs/notify/mark.c | 90 |
1 files changed, 32 insertions, 58 deletions
diff --git a/fs/notify/mark.c b/fs/notify/mark.c index b5b641a2b557..bfb415d0d757 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c | |||
@@ -33,7 +33,7 @@ | |||
33 | * | 33 | * |
34 | * group->mark_mutex | 34 | * group->mark_mutex |
35 | * mark->lock | 35 | * mark->lock |
36 | * inode->i_lock | 36 | * mark->connector->lock |
37 | * | 37 | * |
38 | * group->mark_mutex protects the marks_list anchored inside a given group and | 38 | * group->mark_mutex protects the marks_list anchored inside a given group and |
39 | * each mark is hooked via the g_list. It also protects the groups private | 39 | * each mark is hooked via the g_list. It also protects the groups private |
@@ -44,10 +44,12 @@ | |||
44 | * is assigned to as well as the access to a reference of the inode/vfsmount | 44 | * is assigned to as well as the access to a reference of the inode/vfsmount |
45 | * that is being watched by the mark. | 45 | * that is being watched by the mark. |
46 | * | 46 | * |
47 | * inode->i_lock protects the i_fsnotify_marks list anchored inside a | 47 | * mark->connector->lock protects the list of marks anchored inside an |
48 | * given inode and each mark is hooked via the i_list. (and sorta the | 48 | * inode / vfsmount and each mark is hooked via the i_list. |
49 | * free_i_list) | ||
50 | * | 49 | * |
50 | * A list of notification marks relating to inode / mnt is contained in | ||
51 | * fsnotify_mark_connector. That structure is alive as long as there are any | ||
52 | * marks in the list and is also protected by fsnotify_mark_srcu. | ||
51 | * | 53 | * |
52 | * LIFETIME: | 54 | * LIFETIME: |
53 | * Inode marks survive between when they are added to an inode and when their | 55 | * Inode marks survive between when they are added to an inode and when their |
@@ -110,8 +112,10 @@ static void __fsnotify_recalc_mask(struct fsnotify_mark_connector *conn) | |||
110 | u32 new_mask = 0; | 112 | u32 new_mask = 0; |
111 | struct fsnotify_mark *mark; | 113 | struct fsnotify_mark *mark; |
112 | 114 | ||
115 | assert_spin_locked(&conn->lock); | ||
113 | hlist_for_each_entry(mark, &conn->list, obj_list) | 116 | hlist_for_each_entry(mark, &conn->list, obj_list) |
114 | new_mask |= mark->mask; | 117 | new_mask |= mark->mask; |
118 | |||
115 | if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) | 119 | if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) |
116 | conn->inode->i_fsnotify_mask = new_mask; | 120 | conn->inode->i_fsnotify_mask = new_mask; |
117 | else if (conn->flags & FSNOTIFY_OBJ_TYPE_VFSMOUNT) | 121 | else if (conn->flags & FSNOTIFY_OBJ_TYPE_VFSMOUNT) |
@@ -128,31 +132,20 @@ void fsnotify_recalc_mask(struct fsnotify_mark_connector *conn) | |||
128 | if (!conn) | 132 | if (!conn) |
129 | return; | 133 | return; |
130 | 134 | ||
131 | if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) | 135 | spin_lock(&conn->lock); |
132 | spin_lock(&conn->inode->i_lock); | ||
133 | else | ||
134 | spin_lock(&conn->mnt->mnt_root->d_lock); | ||
135 | __fsnotify_recalc_mask(conn); | 136 | __fsnotify_recalc_mask(conn); |
136 | if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) { | 137 | spin_unlock(&conn->lock); |
137 | spin_unlock(&conn->inode->i_lock); | 138 | if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) |
138 | __fsnotify_update_child_dentry_flags(conn->inode); | 139 | __fsnotify_update_child_dentry_flags(conn->inode); |
139 | } else { | ||
140 | spin_unlock(&conn->mnt->mnt_root->d_lock); | ||
141 | } | ||
142 | } | 140 | } |
143 | 141 | ||
144 | static struct inode *fsnotify_detach_from_object(struct fsnotify_mark *mark) | 142 | static struct inode *fsnotify_detach_from_object(struct fsnotify_mark *mark) |
145 | { | 143 | { |
146 | struct fsnotify_mark_connector *conn; | 144 | struct fsnotify_mark_connector *conn; |
147 | struct inode *inode = NULL; | 145 | struct inode *inode = NULL; |
148 | spinlock_t *lock; | ||
149 | 146 | ||
150 | conn = mark->connector; | 147 | conn = mark->connector; |
151 | if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) | 148 | spin_lock(&conn->lock); |
152 | lock = &conn->inode->i_lock; | ||
153 | else | ||
154 | lock = &conn->mnt->mnt_root->d_lock; | ||
155 | spin_lock(lock); | ||
156 | hlist_del_init_rcu(&mark->obj_list); | 149 | hlist_del_init_rcu(&mark->obj_list); |
157 | if (hlist_empty(&conn->list)) { | 150 | if (hlist_empty(&conn->list)) { |
158 | if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) | 151 | if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) |
@@ -160,7 +153,7 @@ static struct inode *fsnotify_detach_from_object(struct fsnotify_mark *mark) | |||
160 | } | 153 | } |
161 | __fsnotify_recalc_mask(conn); | 154 | __fsnotify_recalc_mask(conn); |
162 | mark->connector = NULL; | 155 | mark->connector = NULL; |
163 | spin_unlock(lock); | 156 | spin_unlock(&conn->lock); |
164 | 157 | ||
165 | return inode; | 158 | return inode; |
166 | } | 159 | } |
@@ -326,7 +319,6 @@ int fsnotify_compare_groups(struct fsnotify_group *a, struct fsnotify_group *b) | |||
326 | 319 | ||
327 | static int fsnotify_attach_connector_to_object( | 320 | static int fsnotify_attach_connector_to_object( |
328 | struct fsnotify_mark_connector **connp, | 321 | struct fsnotify_mark_connector **connp, |
329 | spinlock_t *lock, | ||
330 | struct inode *inode, | 322 | struct inode *inode, |
331 | struct vfsmount *mnt) | 323 | struct vfsmount *mnt) |
332 | { | 324 | { |
@@ -335,6 +327,7 @@ static int fsnotify_attach_connector_to_object( | |||
335 | conn = kmem_cache_alloc(fsnotify_mark_connector_cachep, GFP_KERNEL); | 327 | conn = kmem_cache_alloc(fsnotify_mark_connector_cachep, GFP_KERNEL); |
336 | if (!conn) | 328 | if (!conn) |
337 | return -ENOMEM; | 329 | return -ENOMEM; |
330 | spin_lock_init(&conn->lock); | ||
338 | INIT_HLIST_HEAD(&conn->list); | 331 | INIT_HLIST_HEAD(&conn->list); |
339 | if (inode) { | 332 | if (inode) { |
340 | conn->flags = FSNOTIFY_OBJ_TYPE_INODE; | 333 | conn->flags = FSNOTIFY_OBJ_TYPE_INODE; |
@@ -344,16 +337,13 @@ static int fsnotify_attach_connector_to_object( | |||
344 | conn->mnt = mnt; | 337 | conn->mnt = mnt; |
345 | } | 338 | } |
346 | /* | 339 | /* |
347 | * Make sure 'conn' initialization is visible. Matches | 340 | * cmpxchg() provides the barrier so that readers of *connp can see |
348 | * lockless_dereference() in fsnotify(). | 341 | * only initialized structure |
349 | */ | 342 | */ |
350 | smp_wmb(); | 343 | if (cmpxchg(connp, NULL, conn)) { |
351 | spin_lock(lock); | 344 | /* Someone else created list structure for us */ |
352 | if (!*connp) | ||
353 | *connp = conn; | ||
354 | else | ||
355 | kmem_cache_free(fsnotify_mark_connector_cachep, conn); | 345 | kmem_cache_free(fsnotify_mark_connector_cachep, conn); |
356 | spin_unlock(lock); | 346 | } |
357 | 347 | ||
358 | return 0; | 348 | return 0; |
359 | } | 349 | } |
@@ -371,35 +361,30 @@ static int fsnotify_add_mark_list(struct fsnotify_mark *mark, | |||
371 | struct fsnotify_mark *lmark, *last = NULL; | 361 | struct fsnotify_mark *lmark, *last = NULL; |
372 | struct fsnotify_mark_connector *conn; | 362 | struct fsnotify_mark_connector *conn; |
373 | struct fsnotify_mark_connector **connp; | 363 | struct fsnotify_mark_connector **connp; |
374 | spinlock_t *lock; | ||
375 | int cmp; | 364 | int cmp; |
376 | int err = 0; | 365 | int err = 0; |
377 | 366 | ||
378 | if (WARN_ON(!inode && !mnt)) | 367 | if (WARN_ON(!inode && !mnt)) |
379 | return -EINVAL; | 368 | return -EINVAL; |
380 | if (inode) { | 369 | if (inode) |
381 | connp = &inode->i_fsnotify_marks; | 370 | connp = &inode->i_fsnotify_marks; |
382 | lock = &inode->i_lock; | 371 | else |
383 | } else { | ||
384 | connp = &real_mount(mnt)->mnt_fsnotify_marks; | 372 | connp = &real_mount(mnt)->mnt_fsnotify_marks; |
385 | lock = &mnt->mnt_root->d_lock; | ||
386 | } | ||
387 | 373 | ||
388 | if (!*connp) { | 374 | if (!*connp) { |
389 | err = fsnotify_attach_connector_to_object(connp, lock, | 375 | err = fsnotify_attach_connector_to_object(connp, inode, mnt); |
390 | inode, mnt); | ||
391 | if (err) | 376 | if (err) |
392 | return err; | 377 | return err; |
393 | } | 378 | } |
394 | spin_lock(&mark->lock); | 379 | spin_lock(&mark->lock); |
395 | spin_lock(lock); | ||
396 | conn = *connp; | 380 | conn = *connp; |
381 | spin_lock(&conn->lock); | ||
397 | 382 | ||
398 | /* is mark the first mark? */ | 383 | /* is mark the first mark? */ |
399 | if (hlist_empty(&conn->list)) { | 384 | if (hlist_empty(&conn->list)) { |
400 | hlist_add_head_rcu(&mark->obj_list, &conn->list); | 385 | hlist_add_head_rcu(&mark->obj_list, &conn->list); |
401 | if (inode) | 386 | if (inode) |
402 | __iget(inode); | 387 | igrab(inode); |
403 | goto added; | 388 | goto added; |
404 | } | 389 | } |
405 | 390 | ||
@@ -425,7 +410,7 @@ static int fsnotify_add_mark_list(struct fsnotify_mark *mark, | |||
425 | added: | 410 | added: |
426 | mark->connector = conn; | 411 | mark->connector = conn; |
427 | out_err: | 412 | out_err: |
428 | spin_unlock(lock); | 413 | spin_unlock(&conn->lock); |
429 | spin_unlock(&mark->lock); | 414 | spin_unlock(&mark->lock); |
430 | return err; | 415 | return err; |
431 | } | 416 | } |
@@ -449,7 +434,7 @@ int fsnotify_add_mark_locked(struct fsnotify_mark *mark, | |||
449 | * LOCKING ORDER!!!! | 434 | * LOCKING ORDER!!!! |
450 | * group->mark_mutex | 435 | * group->mark_mutex |
451 | * mark->lock | 436 | * mark->lock |
452 | * inode->i_lock | 437 | * mark->connector->lock |
453 | */ | 438 | */ |
454 | spin_lock(&mark->lock); | 439 | spin_lock(&mark->lock); |
455 | mark->flags |= FSNOTIFY_MARK_FLAG_ALIVE | FSNOTIFY_MARK_FLAG_ATTACHED; | 440 | mark->flags |= FSNOTIFY_MARK_FLAG_ALIVE | FSNOTIFY_MARK_FLAG_ATTACHED; |
@@ -505,24 +490,19 @@ struct fsnotify_mark *fsnotify_find_mark(struct fsnotify_mark_connector *conn, | |||
505 | struct fsnotify_group *group) | 490 | struct fsnotify_group *group) |
506 | { | 491 | { |
507 | struct fsnotify_mark *mark; | 492 | struct fsnotify_mark *mark; |
508 | spinlock_t *lock; | ||
509 | 493 | ||
510 | if (!conn) | 494 | if (!conn) |
511 | return NULL; | 495 | return NULL; |
512 | 496 | ||
513 | if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) | 497 | spin_lock(&conn->lock); |
514 | lock = &conn->inode->i_lock; | ||
515 | else | ||
516 | lock = &conn->mnt->mnt_root->d_lock; | ||
517 | spin_lock(lock); | ||
518 | hlist_for_each_entry(mark, &conn->list, obj_list) { | 498 | hlist_for_each_entry(mark, &conn->list, obj_list) { |
519 | if (mark->group == group) { | 499 | if (mark->group == group) { |
520 | fsnotify_get_mark(mark); | 500 | fsnotify_get_mark(mark); |
521 | spin_unlock(lock); | 501 | spin_unlock(&conn->lock); |
522 | return mark; | 502 | return mark; |
523 | } | 503 | } |
524 | } | 504 | } |
525 | spin_unlock(lock); | 505 | spin_unlock(&conn->lock); |
526 | return NULL; | 506 | return NULL; |
527 | } | 507 | } |
528 | 508 | ||
@@ -595,16 +575,10 @@ void fsnotify_detach_group_marks(struct fsnotify_group *group) | |||
595 | void fsnotify_destroy_marks(struct fsnotify_mark_connector *conn) | 575 | void fsnotify_destroy_marks(struct fsnotify_mark_connector *conn) |
596 | { | 576 | { |
597 | struct fsnotify_mark *mark; | 577 | struct fsnotify_mark *mark; |
598 | spinlock_t *lock; | ||
599 | 578 | ||
600 | if (!conn) | 579 | if (!conn) |
601 | return; | 580 | return; |
602 | 581 | ||
603 | if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) | ||
604 | lock = &conn->inode->i_lock; | ||
605 | else | ||
606 | lock = &conn->mnt->mnt_root->d_lock; | ||
607 | |||
608 | while (1) { | 582 | while (1) { |
609 | /* | 583 | /* |
610 | * We have to be careful since we can race with e.g. | 584 | * We have to be careful since we can race with e.g. |
@@ -613,15 +587,15 @@ void fsnotify_destroy_marks(struct fsnotify_mark_connector *conn) | |||
613 | * we are holding mark reference so mark cannot be freed and | 587 | * we are holding mark reference so mark cannot be freed and |
614 | * calling fsnotify_destroy_mark() more than once is fine. | 588 | * calling fsnotify_destroy_mark() more than once is fine. |
615 | */ | 589 | */ |
616 | spin_lock(lock); | 590 | spin_lock(&conn->lock); |
617 | if (hlist_empty(&conn->list)) { | 591 | if (hlist_empty(&conn->list)) { |
618 | spin_unlock(lock); | 592 | spin_unlock(&conn->lock); |
619 | break; | 593 | break; |
620 | } | 594 | } |
621 | mark = hlist_entry(conn->list.first, struct fsnotify_mark, | 595 | mark = hlist_entry(conn->list.first, struct fsnotify_mark, |
622 | obj_list); | 596 | obj_list); |
623 | fsnotify_get_mark(mark); | 597 | fsnotify_get_mark(mark); |
624 | spin_unlock(lock); | 598 | spin_unlock(&conn->lock); |
625 | fsnotify_destroy_mark(mark, mark->group); | 599 | fsnotify_destroy_mark(mark, mark->group); |
626 | fsnotify_put_mark(mark); | 600 | fsnotify_put_mark(mark); |
627 | } | 601 | } |