| Eric Paris | 9058652 | 2009-05-21 17:01:20 -0400 | [diff] [blame] | 1 | /* | 
 | 2 |  *  Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com> | 
 | 3 |  * | 
 | 4 |  *  This program is free software; you can redistribute it and/or modify | 
 | 5 |  *  it under the terms of the GNU General Public License as published by | 
 | 6 |  *  the Free Software Foundation; either version 2, or (at your option) | 
 | 7 |  *  any later version. | 
 | 8 |  * | 
 | 9 |  *  This program is distributed in the hope that it will be useful, | 
 | 10 |  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 11 |  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 | 12 |  *  GNU General Public License for more details. | 
 | 13 |  * | 
 | 14 |  *  You should have received a copy of the GNU General Public License | 
 | 15 |  *  along with this program; see the file COPYING.  If not, write to | 
 | 16 |  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | 
 | 17 |  */ | 
 | 18 |  | 
 | 19 | #include <linux/list.h> | 
 | 20 | #include <linux/mutex.h> | 
 | 21 | #include <linux/slab.h> | 
 | 22 | #include <linux/srcu.h> | 
 | 23 | #include <linux/rculist.h> | 
 | 24 | #include <linux/wait.h> | 
 | 25 |  | 
 | 26 | #include <linux/fsnotify_backend.h> | 
 | 27 | #include "fsnotify.h" | 
 | 28 |  | 
 | 29 | #include <asm/atomic.h> | 
 | 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 | /* | 
| Eric Paris | 3be25f4 | 2009-05-21 17:01:26 -0400 | [diff] [blame] | 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 | /* | 
| Eric Paris | 9058652 | 2009-05-21 17:01:20 -0400 | [diff] [blame] | 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 | /* | 
 | 90 |  * Final freeing of a group | 
 | 91 |  */ | 
| Eric Paris | 3be25f4 | 2009-05-21 17:01:26 -0400 | [diff] [blame] | 92 | void fsnotify_final_destroy_group(struct fsnotify_group *group) | 
| Eric Paris | 9058652 | 2009-05-21 17:01:20 -0400 | [diff] [blame] | 93 | { | 
| Eric Paris | a2d8bc6 | 2009-05-21 17:01:37 -0400 | [diff] [blame] | 94 | 	/* clear the notification queue of all events */ | 
 | 95 | 	fsnotify_flush_notify(group); | 
 | 96 |  | 
| Eric Paris | 9058652 | 2009-05-21 17:01:20 -0400 | [diff] [blame] | 97 | 	if (group->ops->free_group_priv) | 
 | 98 | 		group->ops->free_group_priv(group); | 
 | 99 |  | 
 | 100 | 	kfree(group); | 
 | 101 | } | 
 | 102 |  | 
 | 103 | /* | 
| Eric Paris | 3be25f4 | 2009-05-21 17:01:26 -0400 | [diff] [blame] | 104 |  * Trying to get rid of a group.  We need to first get rid of any outstanding | 
 | 105 |  * allocations and then free the group.  Remember that fsnotify_clear_marks_by_group | 
 | 106 |  * could miss marks that are being freed by inode and those marks could still | 
 | 107 |  * hold a reference to this group (via group->num_marks)  If we get into that | 
 | 108 |  * situtation, the fsnotify_final_destroy_group will get called when that final | 
 | 109 |  * mark is freed. | 
 | 110 |  */ | 
 | 111 | static void fsnotify_destroy_group(struct fsnotify_group *group) | 
 | 112 | { | 
 | 113 | 	/* clear all inode mark entries for this group */ | 
 | 114 | 	fsnotify_clear_marks_by_group(group); | 
 | 115 |  | 
 | 116 | 	/* past the point of no return, matches the initial value of 1 */ | 
 | 117 | 	if (atomic_dec_and_test(&group->num_marks)) | 
 | 118 | 		fsnotify_final_destroy_group(group); | 
 | 119 | } | 
 | 120 |  | 
 | 121 | /* | 
| Eric Paris | 9058652 | 2009-05-21 17:01:20 -0400 | [diff] [blame] | 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. | 
 | 149 |  */ | 
 | 150 | void fsnotify_put_group(struct fsnotify_group *group) | 
 | 151 | { | 
 | 152 | 	if (!atomic_dec_and_mutex_lock(&group->refcnt, &fsnotify_grp_mutex)) | 
 | 153 | 		return; | 
 | 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 | } | 
 | 198 |  | 
 | 199 | /* | 
 | 200 |  * Either finds an existing group which matches the group_num, mask, and ops or | 
 | 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 |  */ | 
 | 204 | struct fsnotify_group *fsnotify_obtain_group(unsigned int group_num, __u32 mask, | 
 | 205 | 					     const struct fsnotify_ops *ops) | 
 | 206 | { | 
 | 207 | 	struct fsnotify_group *group, *tgroup; | 
 | 208 |  | 
 | 209 | 	/* very low use, simpler locking if we just always alloc */ | 
 | 210 | 	group = kmalloc(sizeof(struct fsnotify_group), GFP_KERNEL); | 
 | 211 | 	if (!group) | 
 | 212 | 		return ERR_PTR(-ENOMEM); | 
 | 213 |  | 
 | 214 | 	atomic_set(&group->refcnt, 1); | 
 | 215 |  | 
 | 216 | 	group->on_group_list = 0; | 
 | 217 | 	group->group_num = group_num; | 
 | 218 | 	group->mask = mask; | 
 | 219 |  | 
| Eric Paris | a2d8bc6 | 2009-05-21 17:01:37 -0400 | [diff] [blame] | 220 | 	mutex_init(&group->notification_mutex); | 
 | 221 | 	INIT_LIST_HEAD(&group->notification_list); | 
 | 222 | 	init_waitqueue_head(&group->notification_waitq); | 
 | 223 | 	group->q_len = 0; | 
 | 224 | 	group->max_events = UINT_MAX; | 
 | 225 |  | 
| Eric Paris | 3be25f4 | 2009-05-21 17:01:26 -0400 | [diff] [blame] | 226 | 	spin_lock_init(&group->mark_lock); | 
 | 227 | 	atomic_set(&group->num_marks, 0); | 
 | 228 | 	INIT_LIST_HEAD(&group->mark_entries); | 
 | 229 |  | 
| Eric Paris | 9058652 | 2009-05-21 17:01:20 -0400 | [diff] [blame] | 230 | 	group->ops = ops; | 
 | 231 |  | 
 | 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; | 
| Eric Paris | 3be25f4 | 2009-05-21 17:01:26 -0400 | [diff] [blame] | 245 | 	/* being on the fsnotify_groups list holds one num_marks */ | 
 | 246 | 	atomic_inc(&group->num_marks); | 
| Eric Paris | 9058652 | 2009-05-21 17:01:20 -0400 | [diff] [blame] | 247 |  | 
 | 248 | 	mutex_unlock(&fsnotify_grp_mutex); | 
 | 249 |  | 
 | 250 | 	if (mask) | 
 | 251 | 		fsnotify_recalc_global_mask(); | 
 | 252 |  | 
 | 253 | 	return group; | 
 | 254 | } |