|
@@ -357,6 +357,77 @@ static int inotify_find_inode(const char __user *dirname, struct path *path, uns
|
|
|
return error;
|
|
|
}
|
|
|
|
|
|
+static int inotify_add_to_idr(struct idr *idr, spinlock_t *idr_lock,
|
|
|
+ int last_wd,
|
|
|
+ struct inotify_inode_mark_entry *ientry)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ do {
|
|
|
+ if (unlikely(!idr_pre_get(idr, GFP_KERNEL)))
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ spin_lock(idr_lock);
|
|
|
+ ret = idr_get_new_above(idr, ientry, last_wd + 1,
|
|
|
+ &ientry->wd);
|
|
|
+ /* we added the mark to the idr, take a reference */
|
|
|
+ if (!ret)
|
|
|
+ fsnotify_get_mark(&ientry->fsn_entry);
|
|
|
+ spin_unlock(idr_lock);
|
|
|
+ } while (ret == -EAGAIN);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static struct inotify_inode_mark_entry *inotify_idr_find_locked(struct fsnotify_group *group,
|
|
|
+ int wd)
|
|
|
+{
|
|
|
+ struct idr *idr = &group->inotify_data.idr;
|
|
|
+ spinlock_t *idr_lock = &group->inotify_data.idr_lock;
|
|
|
+ struct inotify_inode_mark_entry *ientry;
|
|
|
+
|
|
|
+ assert_spin_locked(idr_lock);
|
|
|
+
|
|
|
+ ientry = idr_find(idr, wd);
|
|
|
+ if (ientry) {
|
|
|
+ struct fsnotify_mark_entry *fsn_entry = &ientry->fsn_entry;
|
|
|
+
|
|
|
+ fsnotify_get_mark(fsn_entry);
|
|
|
+ /* One ref for being in the idr, one ref we just took */
|
|
|
+ BUG_ON(atomic_read(&fsn_entry->refcnt) < 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ientry;
|
|
|
+}
|
|
|
+
|
|
|
+static struct inotify_inode_mark_entry *inotify_idr_find(struct fsnotify_group *group,
|
|
|
+ int wd)
|
|
|
+{
|
|
|
+ struct inotify_inode_mark_entry *ientry;
|
|
|
+ spinlock_t *idr_lock = &group->inotify_data.idr_lock;
|
|
|
+
|
|
|
+ spin_lock(idr_lock);
|
|
|
+ ientry = inotify_idr_find_locked(group, wd);
|
|
|
+ spin_unlock(idr_lock);
|
|
|
+
|
|
|
+ return ientry;
|
|
|
+}
|
|
|
+
|
|
|
+static void do_inotify_remove_from_idr(struct fsnotify_group *group,
|
|
|
+ struct inotify_inode_mark_entry *ientry)
|
|
|
+{
|
|
|
+ struct idr *idr = &group->inotify_data.idr;
|
|
|
+ spinlock_t *idr_lock = &group->inotify_data.idr_lock;
|
|
|
+ int wd = ientry->wd;
|
|
|
+
|
|
|
+ assert_spin_locked(idr_lock);
|
|
|
+
|
|
|
+ idr_remove(idr, wd);
|
|
|
+
|
|
|
+ /* removed from the idr, drop that ref */
|
|
|
+ fsnotify_put_mark(&ientry->fsn_entry);
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* Remove the mark from the idr (if present) and drop the reference
|
|
|
* on the mark because it was in the idr.
|
|
@@ -364,42 +435,72 @@ static int inotify_find_inode(const char __user *dirname, struct path *path, uns
|
|
|
static void inotify_remove_from_idr(struct fsnotify_group *group,
|
|
|
struct inotify_inode_mark_entry *ientry)
|
|
|
{
|
|
|
- struct idr *idr;
|
|
|
- struct fsnotify_mark_entry *entry;
|
|
|
- struct inotify_inode_mark_entry *found_ientry;
|
|
|
+ spinlock_t *idr_lock = &group->inotify_data.idr_lock;
|
|
|
+ struct inotify_inode_mark_entry *found_ientry = NULL;
|
|
|
int wd;
|
|
|
|
|
|
- spin_lock(&group->inotify_data.idr_lock);
|
|
|
- idr = &group->inotify_data.idr;
|
|
|
+ spin_lock(idr_lock);
|
|
|
wd = ientry->wd;
|
|
|
|
|
|
- if (wd == -1)
|
|
|
+ /*
|
|
|
+ * does this ientry think it is in the idr? we shouldn't get called
|
|
|
+ * if it wasn't....
|
|
|
+ */
|
|
|
+ if (wd == -1) {
|
|
|
+ printk(KERN_WARNING "%s: ientry=%p ientry->wd=%d ientry->group=%p"
|
|
|
+ " ientry->inode=%p\n", __func__, ientry, ientry->wd,
|
|
|
+ ientry->fsn_entry.group, ientry->fsn_entry.inode);
|
|
|
+ WARN_ON(1);
|
|
|
goto out;
|
|
|
+ }
|
|
|
|
|
|
- entry = idr_find(&group->inotify_data.idr, wd);
|
|
|
- if (unlikely(!entry))
|
|
|
+ /* Lets look in the idr to see if we find it */
|
|
|
+ found_ientry = inotify_idr_find_locked(group, wd);
|
|
|
+ if (unlikely(!found_ientry)) {
|
|
|
+ printk(KERN_WARNING "%s: ientry=%p ientry->wd=%d ientry->group=%p"
|
|
|
+ " ientry->inode=%p\n", __func__, ientry, ientry->wd,
|
|
|
+ ientry->fsn_entry.group, ientry->fsn_entry.inode);
|
|
|
+ WARN_ON(1);
|
|
|
goto out;
|
|
|
+ }
|
|
|
|
|
|
- found_ientry = container_of(entry, struct inotify_inode_mark_entry, fsn_entry);
|
|
|
+ /*
|
|
|
+ * We found an entry in the idr at the right wd, but it's
|
|
|
+ * not the entry we were told to remove. eparis seriously
|
|
|
+ * fucked up somewhere.
|
|
|
+ */
|
|
|
if (unlikely(found_ientry != ientry)) {
|
|
|
- /* We found an entry in the idr with the right wd, but it's
|
|
|
- * not the entry we were told to remove. eparis seriously
|
|
|
- * fucked up somewhere. */
|
|
|
WARN_ON(1);
|
|
|
- ientry->wd = -1;
|
|
|
+ printk(KERN_WARNING "%s: ientry=%p ientry->wd=%d ientry->group=%p "
|
|
|
+ "entry->inode=%p found_ientry=%p found_ientry->wd=%d "
|
|
|
+ "found_ientry->group=%p found_ientry->inode=%p\n",
|
|
|
+ __func__, ientry, ientry->wd, ientry->fsn_entry.group,
|
|
|
+ ientry->fsn_entry.inode, found_ientry, found_ientry->wd,
|
|
|
+ found_ientry->fsn_entry.group,
|
|
|
+ found_ientry->fsn_entry.inode);
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
- /* One ref for being in the idr, one ref held by the caller */
|
|
|
- BUG_ON(atomic_read(&entry->refcnt) < 2);
|
|
|
-
|
|
|
- idr_remove(idr, wd);
|
|
|
- ientry->wd = -1;
|
|
|
+ /*
|
|
|
+ * One ref for being in the idr
|
|
|
+ * one ref held by the caller trying to kill us
|
|
|
+ * one ref grabbed by inotify_idr_find
|
|
|
+ */
|
|
|
+ if (unlikely(atomic_read(&ientry->fsn_entry.refcnt) < 3)) {
|
|
|
+ printk(KERN_WARNING "%s: ientry=%p ientry->wd=%d ientry->group=%p"
|
|
|
+ " ientry->inode=%p\n", __func__, ientry, ientry->wd,
|
|
|
+ ientry->fsn_entry.group, ientry->fsn_entry.inode);
|
|
|
+ /* we can't really recover with bad ref cnting.. */
|
|
|
+ BUG();
|
|
|
+ }
|
|
|
|
|
|
- /* removed from the idr, drop that ref */
|
|
|
- fsnotify_put_mark(entry);
|
|
|
+ do_inotify_remove_from_idr(group, ientry);
|
|
|
out:
|
|
|
- spin_unlock(&group->inotify_data.idr_lock);
|
|
|
+ /* match the ref taken by inotify_idr_find_locked() */
|
|
|
+ if (found_ientry)
|
|
|
+ fsnotify_put_mark(&found_ientry->fsn_entry);
|
|
|
+ ientry->wd = -1;
|
|
|
+ spin_unlock(idr_lock);
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -524,6 +625,8 @@ static int inotify_new_watch(struct fsnotify_group *group,
|
|
|
struct inotify_inode_mark_entry *tmp_ientry;
|
|
|
__u32 mask;
|
|
|
int ret;
|
|
|
+ struct idr *idr = &group->inotify_data.idr;
|
|
|
+ spinlock_t *idr_lock = &group->inotify_data.idr_lock;
|
|
|
|
|
|
/* don't allow invalid bits: we don't want flags set */
|
|
|
mask = inotify_arg_to_mask(arg);
|
|
@@ -541,28 +644,11 @@ static int inotify_new_watch(struct fsnotify_group *group,
|
|
|
ret = -ENOSPC;
|
|
|
if (atomic_read(&group->inotify_data.user->inotify_watches) >= inotify_max_user_watches)
|
|
|
goto out_err;
|
|
|
-retry:
|
|
|
- ret = -ENOMEM;
|
|
|
- if (unlikely(!idr_pre_get(&group->inotify_data.idr, GFP_KERNEL)))
|
|
|
- goto out_err;
|
|
|
-
|
|
|
- /* we are putting the mark on the idr, take a reference */
|
|
|
- fsnotify_get_mark(&tmp_ientry->fsn_entry);
|
|
|
-
|
|
|
- spin_lock(&group->inotify_data.idr_lock);
|
|
|
- ret = idr_get_new_above(&group->inotify_data.idr, &tmp_ientry->fsn_entry,
|
|
|
- group->inotify_data.last_wd+1,
|
|
|
- &tmp_ientry->wd);
|
|
|
- spin_unlock(&group->inotify_data.idr_lock);
|
|
|
- if (ret) {
|
|
|
- /* we didn't get on the idr, drop the idr reference */
|
|
|
- fsnotify_put_mark(&tmp_ientry->fsn_entry);
|
|
|
|
|
|
- /* idr was out of memory allocate and try again */
|
|
|
- if (ret == -EAGAIN)
|
|
|
- goto retry;
|
|
|
+ ret = inotify_add_to_idr(idr, idr_lock, group->inotify_data.last_wd,
|
|
|
+ tmp_ientry);
|
|
|
+ if (ret)
|
|
|
goto out_err;
|
|
|
- }
|
|
|
|
|
|
/* we are on the idr, now get on the inode */
|
|
|
ret = fsnotify_add_mark(&tmp_ientry->fsn_entry, group, inode);
|
|
@@ -726,7 +812,7 @@ fput_and_out:
|
|
|
SYSCALL_DEFINE2(inotify_rm_watch, int, fd, __s32, wd)
|
|
|
{
|
|
|
struct fsnotify_group *group;
|
|
|
- struct fsnotify_mark_entry *entry;
|
|
|
+ struct inotify_inode_mark_entry *ientry;
|
|
|
struct file *filp;
|
|
|
int ret = 0, fput_needed;
|
|
|
|
|
@@ -735,25 +821,23 @@ SYSCALL_DEFINE2(inotify_rm_watch, int, fd, __s32, wd)
|
|
|
return -EBADF;
|
|
|
|
|
|
/* verify that this is indeed an inotify instance */
|
|
|
- if (unlikely(filp->f_op != &inotify_fops)) {
|
|
|
- ret = -EINVAL;
|
|
|
+ ret = -EINVAL;
|
|
|
+ if (unlikely(filp->f_op != &inotify_fops))
|
|
|
goto out;
|
|
|
- }
|
|
|
|
|
|
group = filp->private_data;
|
|
|
|
|
|
- spin_lock(&group->inotify_data.idr_lock);
|
|
|
- entry = idr_find(&group->inotify_data.idr, wd);
|
|
|
- if (unlikely(!entry)) {
|
|
|
- spin_unlock(&group->inotify_data.idr_lock);
|
|
|
- ret = -EINVAL;
|
|
|
+ ret = -EINVAL;
|
|
|
+ ientry = inotify_idr_find(group, wd);
|
|
|
+ if (unlikely(!ientry))
|
|
|
goto out;
|
|
|
- }
|
|
|
- fsnotify_get_mark(entry);
|
|
|
- spin_unlock(&group->inotify_data.idr_lock);
|
|
|
|
|
|
- fsnotify_destroy_mark_by_entry(entry);
|
|
|
- fsnotify_put_mark(entry);
|
|
|
+ ret = 0;
|
|
|
+
|
|
|
+ fsnotify_destroy_mark_by_entry(&ientry->fsn_entry);
|
|
|
+
|
|
|
+ /* match ref taken by inotify_idr_find */
|
|
|
+ fsnotify_put_mark(&ientry->fsn_entry);
|
|
|
|
|
|
out:
|
|
|
fput_light(filp, fput_needed);
|