diff options
Diffstat (limited to 'fs/notify/group.c')
| -rw-r--r-- | fs/notify/group.c | 182 |
1 files changed, 16 insertions, 166 deletions
diff --git a/fs/notify/group.c b/fs/notify/group.c index 0e1677144bc5..d309f38449cb 100644 --- a/fs/notify/group.c +++ b/fs/notify/group.c | |||
| @@ -28,64 +28,6 @@ | |||
| 28 | 28 | ||
| 29 | #include <asm/atomic.h> | 29 | #include <asm/atomic.h> |
| 30 | 30 | ||
| 31 | /* protects writes to fsnotify_groups and fsnotify_mask */ | ||
| 32 | static DEFINE_MUTEX(fsnotify_grp_mutex); | ||
| 33 | /* protects reads while running the fsnotify_groups list */ | ||
| 34 | struct srcu_struct fsnotify_grp_srcu; | ||
| 35 | /* all groups registered to receive filesystem notifications */ | ||
| 36 | LIST_HEAD(fsnotify_groups); | ||
| 37 | /* bitwise OR of all events (FS_*) interesting to some group on this system */ | ||
| 38 | __u32 fsnotify_mask; | ||
| 39 | |||
| 40 | /* | ||
| 41 | * When a new group registers or changes it's set of interesting events | ||
| 42 | * this function updates the fsnotify_mask to contain all interesting events | ||
| 43 | */ | ||
| 44 | void fsnotify_recalc_global_mask(void) | ||
| 45 | { | ||
| 46 | struct fsnotify_group *group; | ||
| 47 | __u32 mask = 0; | ||
| 48 | int idx; | ||
| 49 | |||
| 50 | idx = srcu_read_lock(&fsnotify_grp_srcu); | ||
| 51 | list_for_each_entry_rcu(group, &fsnotify_groups, group_list) | ||
| 52 | mask |= group->mask; | ||
| 53 | srcu_read_unlock(&fsnotify_grp_srcu, idx); | ||
| 54 | fsnotify_mask = mask; | ||
| 55 | } | ||
| 56 | |||
| 57 | /* | ||
| 58 | * Update the group->mask by running all of the marks associated with this | ||
| 59 | * group and finding the bitwise | of all of the mark->mask. If we change | ||
| 60 | * the group->mask we need to update the global mask of events interesting | ||
| 61 | * to the system. | ||
| 62 | */ | ||
| 63 | void fsnotify_recalc_group_mask(struct fsnotify_group *group) | ||
| 64 | { | ||
| 65 | __u32 mask = 0; | ||
| 66 | __u32 old_mask = group->mask; | ||
| 67 | struct fsnotify_mark_entry *entry; | ||
| 68 | |||
| 69 | spin_lock(&group->mark_lock); | ||
| 70 | list_for_each_entry(entry, &group->mark_entries, g_list) | ||
| 71 | mask |= entry->mask; | ||
| 72 | spin_unlock(&group->mark_lock); | ||
| 73 | |||
| 74 | group->mask = mask; | ||
| 75 | |||
| 76 | if (old_mask != mask) | ||
| 77 | fsnotify_recalc_global_mask(); | ||
| 78 | } | ||
| 79 | |||
| 80 | /* | ||
| 81 | * Take a reference to a group so things found under the fsnotify_grp_mutex | ||
| 82 | * can't get freed under us | ||
| 83 | */ | ||
| 84 | static void fsnotify_get_group(struct fsnotify_group *group) | ||
| 85 | { | ||
| 86 | atomic_inc(&group->refcnt); | ||
| 87 | } | ||
| 88 | |||
| 89 | /* | 31 | /* |
| 90 | * Final freeing of a group | 32 | * Final freeing of a group |
| 91 | */ | 33 | */ |
| @@ -110,145 +52,53 @@ void fsnotify_final_destroy_group(struct fsnotify_group *group) | |||
| 110 | */ | 52 | */ |
| 111 | static void fsnotify_destroy_group(struct fsnotify_group *group) | 53 | static void fsnotify_destroy_group(struct fsnotify_group *group) |
| 112 | { | 54 | { |
| 113 | /* clear all inode mark entries for this group */ | 55 | /* clear all inode marks for this group */ |
| 114 | fsnotify_clear_marks_by_group(group); | 56 | fsnotify_clear_marks_by_group(group); |
| 115 | 57 | ||
| 58 | synchronize_srcu(&fsnotify_mark_srcu); | ||
| 59 | |||
| 116 | /* past the point of no return, matches the initial value of 1 */ | 60 | /* past the point of no return, matches the initial value of 1 */ |
| 117 | if (atomic_dec_and_test(&group->num_marks)) | 61 | if (atomic_dec_and_test(&group->num_marks)) |
| 118 | fsnotify_final_destroy_group(group); | 62 | fsnotify_final_destroy_group(group); |
| 119 | } | 63 | } |
| 120 | 64 | ||
| 121 | /* | 65 | /* |
| 122 | * Remove this group from the global list of groups that will get events | ||
| 123 | * this can be done even if there are still references and things still using | ||
| 124 | * this group. This just stops the group from getting new events. | ||
| 125 | */ | ||
| 126 | static void __fsnotify_evict_group(struct fsnotify_group *group) | ||
| 127 | { | ||
| 128 | BUG_ON(!mutex_is_locked(&fsnotify_grp_mutex)); | ||
| 129 | |||
| 130 | if (group->on_group_list) | ||
| 131 | list_del_rcu(&group->group_list); | ||
| 132 | group->on_group_list = 0; | ||
| 133 | } | ||
| 134 | |||
| 135 | /* | ||
| 136 | * Called when a group is no longer interested in getting events. This can be | ||
| 137 | * used if a group is misbehaving or if for some reason a group should no longer | ||
| 138 | * get any filesystem events. | ||
| 139 | */ | ||
| 140 | void fsnotify_evict_group(struct fsnotify_group *group) | ||
| 141 | { | ||
| 142 | mutex_lock(&fsnotify_grp_mutex); | ||
| 143 | __fsnotify_evict_group(group); | ||
| 144 | mutex_unlock(&fsnotify_grp_mutex); | ||
| 145 | } | ||
| 146 | |||
| 147 | /* | ||
| 148 | * Drop a reference to a group. Free it if it's through. | 66 | * Drop a reference to a group. Free it if it's through. |
| 149 | */ | 67 | */ |
| 150 | void fsnotify_put_group(struct fsnotify_group *group) | 68 | void fsnotify_put_group(struct fsnotify_group *group) |
| 151 | { | 69 | { |
| 152 | if (!atomic_dec_and_mutex_lock(&group->refcnt, &fsnotify_grp_mutex)) | 70 | if (atomic_dec_and_test(&group->refcnt)) |
| 153 | return; | 71 | fsnotify_destroy_group(group); |
| 154 | |||
| 155 | /* | ||
| 156 | * OK, now we know that there's no other users *and* we hold mutex, | ||
| 157 | * so no new references will appear | ||
| 158 | */ | ||
| 159 | __fsnotify_evict_group(group); | ||
| 160 | |||
| 161 | /* | ||
| 162 | * now it's off the list, so the only thing we might care about is | ||
| 163 | * srcu access.... | ||
| 164 | */ | ||
| 165 | mutex_unlock(&fsnotify_grp_mutex); | ||
| 166 | synchronize_srcu(&fsnotify_grp_srcu); | ||
| 167 | |||
| 168 | /* and now it is really dead. _Nothing_ could be seeing it */ | ||
| 169 | fsnotify_recalc_global_mask(); | ||
| 170 | fsnotify_destroy_group(group); | ||
| 171 | } | ||
| 172 | |||
| 173 | /* | ||
| 174 | * Simply run the fsnotify_groups list and find a group which matches | ||
| 175 | * the given parameters. If a group is found we take a reference to that | ||
| 176 | * group. | ||
| 177 | */ | ||
| 178 | static struct fsnotify_group *fsnotify_find_group(unsigned int group_num, __u32 mask, | ||
| 179 | const struct fsnotify_ops *ops) | ||
| 180 | { | ||
| 181 | struct fsnotify_group *group_iter; | ||
| 182 | struct fsnotify_group *group = NULL; | ||
| 183 | |||
| 184 | BUG_ON(!mutex_is_locked(&fsnotify_grp_mutex)); | ||
| 185 | |||
| 186 | list_for_each_entry_rcu(group_iter, &fsnotify_groups, group_list) { | ||
| 187 | if (group_iter->group_num == group_num) { | ||
| 188 | if ((group_iter->mask == mask) && | ||
| 189 | (group_iter->ops == ops)) { | ||
| 190 | fsnotify_get_group(group_iter); | ||
| 191 | group = group_iter; | ||
| 192 | } else | ||
| 193 | group = ERR_PTR(-EEXIST); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | return group; | ||
| 197 | } | 72 | } |
| 198 | 73 | ||
| 199 | /* | 74 | /* |
| 200 | * Either finds an existing group which matches the group_num, mask, and ops or | 75 | * Create a new fsnotify_group and hold a reference for the group returned. |
| 201 | * creates a new group and adds it to the global group list. In either case we | ||
| 202 | * take a reference for the group returned. | ||
| 203 | */ | 76 | */ |
| 204 | struct fsnotify_group *fsnotify_obtain_group(unsigned int group_num, __u32 mask, | 77 | struct fsnotify_group *fsnotify_alloc_group(const struct fsnotify_ops *ops) |
| 205 | const struct fsnotify_ops *ops) | ||
| 206 | { | 78 | { |
| 207 | struct fsnotify_group *group, *tgroup; | 79 | struct fsnotify_group *group; |
| 208 | 80 | ||
| 209 | /* very low use, simpler locking if we just always alloc */ | 81 | group = kzalloc(sizeof(struct fsnotify_group), GFP_KERNEL); |
| 210 | group = kmalloc(sizeof(struct fsnotify_group), GFP_KERNEL); | ||
| 211 | if (!group) | 82 | if (!group) |
| 212 | return ERR_PTR(-ENOMEM); | 83 | return ERR_PTR(-ENOMEM); |
| 213 | 84 | ||
| 85 | /* set to 0 when there a no external references to this group */ | ||
| 214 | atomic_set(&group->refcnt, 1); | 86 | atomic_set(&group->refcnt, 1); |
| 215 | 87 | /* | |
| 216 | group->on_group_list = 0; | 88 | * hits 0 when there are no external references AND no marks for |
| 217 | group->group_num = group_num; | 89 | * this group |
| 218 | group->mask = mask; | 90 | */ |
| 91 | atomic_set(&group->num_marks, 1); | ||
| 219 | 92 | ||
| 220 | mutex_init(&group->notification_mutex); | 93 | mutex_init(&group->notification_mutex); |
| 221 | INIT_LIST_HEAD(&group->notification_list); | 94 | INIT_LIST_HEAD(&group->notification_list); |
| 222 | init_waitqueue_head(&group->notification_waitq); | 95 | init_waitqueue_head(&group->notification_waitq); |
| 223 | group->q_len = 0; | ||
| 224 | group->max_events = UINT_MAX; | 96 | group->max_events = UINT_MAX; |
| 225 | 97 | ||
| 226 | spin_lock_init(&group->mark_lock); | 98 | spin_lock_init(&group->mark_lock); |
| 227 | atomic_set(&group->num_marks, 0); | 99 | INIT_LIST_HEAD(&group->marks_list); |
| 228 | INIT_LIST_HEAD(&group->mark_entries); | ||
| 229 | 100 | ||
| 230 | group->ops = ops; | 101 | group->ops = ops; |
| 231 | 102 | ||
| 232 | mutex_lock(&fsnotify_grp_mutex); | ||
| 233 | tgroup = fsnotify_find_group(group_num, mask, ops); | ||
| 234 | if (tgroup) { | ||
| 235 | /* group already exists */ | ||
| 236 | mutex_unlock(&fsnotify_grp_mutex); | ||
| 237 | /* destroy the new one we made */ | ||
| 238 | fsnotify_put_group(group); | ||
| 239 | return tgroup; | ||
| 240 | } | ||
| 241 | |||
| 242 | /* group not found, add a new one */ | ||
| 243 | list_add_rcu(&group->group_list, &fsnotify_groups); | ||
| 244 | group->on_group_list = 1; | ||
| 245 | /* being on the fsnotify_groups list holds one num_marks */ | ||
| 246 | atomic_inc(&group->num_marks); | ||
| 247 | |||
| 248 | mutex_unlock(&fsnotify_grp_mutex); | ||
| 249 | |||
| 250 | if (mask) | ||
| 251 | fsnotify_recalc_global_mask(); | ||
| 252 | |||
| 253 | return group; | 103 | return group; |
| 254 | } | 104 | } |
