summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Rothwell <sfr@canb.auug.org.au>2011-02-02 13:31:45 +1100
committerStephen Rothwell <sfr@canb.auug.org.au>2011-02-02 13:31:45 +1100
commit0ddbdcc3bd7ec01bf61fc84ac43b7ecd6ea9339d (patch)
tree7b13f98bca45544ec3463f5d7b3f3e4c953f82d6
parent6cb85d73dc927c9acb22d3c8f52089cf3b2ff9e7 (diff)
parentef9bf3b7144bee6ce1da5616015cabc8771206af (diff)
Merge remote branch 'fsnotify/for-next'
-rw-r--r--fs/cachefiles/namei.c1
-rw-r--r--fs/cachefiles/xattr.c1
-rw-r--r--fs/nfsd/vfs.c3
-rw-r--r--fs/notify/dnotify/dnotify.c15
-rw-r--r--fs/notify/fanotify/fanotify.c24
-rw-r--r--fs/notify/fanotify/fanotify_user.c101
-rw-r--r--fs/notify/group.c1
-rw-r--r--fs/notify/mark.c36
-rw-r--r--include/linux/fanotify.h5
-rw-r--r--include/linux/fsnotify_backend.h14
10 files changed, 121 insertions, 80 deletions
diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
index a0358c2189cb..3f458310e287 100644
--- a/fs/cachefiles/namei.c
+++ b/fs/cachefiles/namei.c
@@ -13,7 +13,6 @@
#include <linux/sched.h>
#include <linux/file.h>
#include <linux/fs.h>
-#include <linux/fsnotify.h>
#include <linux/quotaops.h>
#include <linux/xattr.h>
#include <linux/mount.h>
diff --git a/fs/cachefiles/xattr.c b/fs/cachefiles/xattr.c
index e18b183b47e1..6e050686e9b3 100644
--- a/fs/cachefiles/xattr.c
+++ b/fs/cachefiles/xattr.c
@@ -13,7 +13,6 @@
#include <linux/sched.h>
#include <linux/file.h>
#include <linux/fs.h>
-#include <linux/fsnotify.h>
#include <linux/quotaops.h>
#include <linux/xattr.h>
#include <linux/slab.h>
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 641117f2188d..f38d5dfaccdf 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -19,7 +19,6 @@
#include <linux/fcntl.h>
#include <linux/namei.h>
#include <linux/delay.h>
-#include <linux/fsnotify.h>
#include <linux/posix_acl_xattr.h>
#include <linux/xattr.h>
#include <linux/jhash.h>
@@ -906,7 +905,6 @@ nfsd_vfs_read(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
nfsdstats.io_read += host_err;
*count = host_err;
err = 0;
- fsnotify_access(file);
} else
err = nfserrno(host_err);
return err;
@@ -1008,7 +1006,6 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
goto out_nfserr;
*cnt = host_err;
nfsdstats.io_write += host_err;
- fsnotify_modify(file);
/* clear setuid/setgid flag after write */
if (inode->i_mode & (S_ISUID | S_ISGID))
diff --git a/fs/notify/dnotify/dnotify.c b/fs/notify/dnotify/dnotify.c
index 3344bdd5506e..89ec7e0c15bc 100644
--- a/fs/notify/dnotify/dnotify.c
+++ b/fs/notify/dnotify/dnotify.c
@@ -31,7 +31,6 @@ int dir_notify_enable __read_mostly = 1;
static struct kmem_cache *dnotify_struct_cache __read_mostly;
static struct kmem_cache *dnotify_mark_cache __read_mostly;
static struct fsnotify_group *dnotify_group __read_mostly;
-static DEFINE_MUTEX(dnotify_mark_mutex);
/*
* dnotify will attach one of these to each inode (i_fsnotify_marks) which
@@ -183,7 +182,7 @@ void dnotify_flush(struct file *filp, fl_owner_t id)
return;
dn_mark = container_of(fsn_mark, struct dnotify_mark, fsn_mark);
- mutex_lock(&dnotify_mark_mutex);
+ mutex_lock(&dnotify_group->mutex);
spin_lock(&fsn_mark->lock);
prev = &dn_mark->dn;
@@ -199,11 +198,11 @@ void dnotify_flush(struct file *filp, fl_owner_t id)
spin_unlock(&fsn_mark->lock);
- /* nothing else could have found us thanks to the dnotify_mark_mutex */
+ /* nothing else could have found us thanks to the dnotify_group mutex */
if (dn_mark->dn == NULL)
fsnotify_destroy_mark(fsn_mark);
- mutex_unlock(&dnotify_mark_mutex);
+ mutex_unlock(&dnotify_group->mutex);
fsnotify_put_mark(fsn_mark);
}
@@ -326,7 +325,7 @@ int fcntl_dirnotify(int fd, struct file *filp, unsigned long arg)
new_dn_mark->dn = NULL;
/* this is needed to prevent the fcntl/close race described below */
- mutex_lock(&dnotify_mark_mutex);
+ mutex_lock(&dnotify_group->mutex);
/* add the new_fsn_mark or find an old one. */
fsn_mark = fsnotify_find_inode_mark(dnotify_group, inode);
@@ -348,8 +347,8 @@ int fcntl_dirnotify(int fd, struct file *filp, unsigned long arg)
/* if (f != filp) means that we lost a race and another task/thread
* actually closed the fd we are still playing with before we grabbed
- * the dnotify_mark_mutex and fsn_mark->lock. Since closing the fd is the
- * only time we clean up the marks we need to get our mark off
+ * the dnotify_group mutex and fsn_mark->lock. Since closing the fd is
+ * the only time we clean up the marks we need to get our mark off
* the list. */
if (f != filp) {
/* if we added ourselves, shoot ourselves, it's possible that
@@ -387,7 +386,7 @@ out:
if (destroy)
fsnotify_destroy_mark(fsn_mark);
- mutex_unlock(&dnotify_mark_mutex);
+ mutex_unlock(&dnotify_group->mutex);
fsnotify_put_mark(fsn_mark);
out_err:
if (new_fsn_mark)
diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index f35794b97e8e..c2ba86a972e1 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -184,13 +184,6 @@ static bool fanotify_should_send_event(struct fsnotify_group *group,
marks_mask = (vfsmnt_mark->mask | inode_mark->mask);
marks_ignored_mask = (vfsmnt_mark->ignored_mask | inode_mark->ignored_mask);
} else if (inode_mark) {
- /*
- * if the event is for a child and this inode doesn't care about
- * events on the child, don't send it!
- */
- if ((event_mask & FS_EVENT_ON_CHILD) &&
- !(inode_mark->mask & FS_EVENT_ON_CHILD))
- return false;
marks_mask = inode_mark->mask;
marks_ignored_mask = inode_mark->ignored_mask;
} else if (vfsmnt_mark) {
@@ -204,10 +197,21 @@ static bool fanotify_should_send_event(struct fsnotify_group *group,
(marks_ignored_mask & FS_ISDIR))
return false;
- if (event_mask & marks_mask & ~marks_ignored_mask)
- return true;
+ /*
+ * if the event is for a child and this inode doesn't care about
+ * events on the child, don't send it!
+ */
+ if ((event_mask & FS_EVENT_ON_CHILD) &&
+ !(marks_mask & FS_EVENT_ON_CHILD))
+ return false;
- return false;
+ /*
+ * It might seem logical to check:
+ * if (event_mask & marks_mask & ~marks_ignored_mask)
+ * return true;
+ * but we we know this was true from the caller so just return true.
+ */
+ return true;
}
static void fanotify_free_group_priv(struct fsnotify_group *group)
diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c
index 8b61220cffc5..2d4925b98bdb 100644
--- a/fs/notify/fanotify/fanotify_user.c
+++ b/fs/notify/fanotify/fanotify_user.c
@@ -62,6 +62,7 @@ static int create_fd(struct fsnotify_group *group, struct fsnotify_event *event)
struct dentry *dentry;
struct vfsmount *mnt;
struct file *new_file;
+ unsigned int flags;
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
@@ -83,12 +84,22 @@ static int create_fd(struct fsnotify_group *group, struct fsnotify_event *event)
mnt = mntget(event->path.mnt);
/* it's possible this event was an overflow event. in that case dentry and mnt
* are NULL; That's fine, just don't call dentry open */
- if (dentry && mnt)
- new_file = dentry_open(dentry, mnt,
- group->fanotify_data.f_flags | FMODE_NONOTIFY,
- current_cred());
- else
+ if (dentry && mnt) {
+ flags = group->fanotify_data.f_flags;
+ new_file = dentry_open(dentry, mnt, flags, current_cred());
+ /*
+ * Attempt fallback to read-only access if writable was not possible
+ * in order to at least provide something to the listener.
+ */
+ if (IS_ERR(new_file) && group->fanotify_data.readonly_fallback) {
+ flags &= ~O_ACCMODE;
+ flags |= O_RDONLY;
+ new_file = dentry_open(dentry, mnt, flags,
+ current_cred());
+ }
+ } else {
new_file = ERR_PTR(-EOVERFLOW);
+ }
if (IS_ERR(new_file)) {
/*
* we still send an event even if we can't open the file. this
@@ -208,14 +219,6 @@ static int prepare_for_access_response(struct fsnotify_group *group,
re->fd = fd;
mutex_lock(&group->fanotify_data.access_mutex);
-
- if (atomic_read(&group->fanotify_data.bypass_perm)) {
- mutex_unlock(&group->fanotify_data.access_mutex);
- kmem_cache_free(fanotify_response_event_cache, re);
- event->response = FAN_ALLOW;
- return 0;
- }
-
list_add_tail(&re->list, &group->fanotify_data.access_list);
mutex_unlock(&group->fanotify_data.access_mutex);
@@ -516,6 +519,7 @@ static __u32 fanotify_mark_remove_from_mask(struct fsnotify_mark *fsn_mark,
unsigned int flags)
{
__u32 oldmask;
+ int destroy_mark;
spin_lock(&fsn_mark->lock);
if (!(flags & FAN_MARK_IGNORED_MASK)) {
@@ -525,9 +529,10 @@ static __u32 fanotify_mark_remove_from_mask(struct fsnotify_mark *fsn_mark,
oldmask = fsn_mark->ignored_mask;
fsnotify_set_mark_ignored_mask_locked(fsn_mark, (oldmask & ~mask));
}
+ destroy_mark = (!fsn_mark->mask && !fsn_mark->ignored_mask);
spin_unlock(&fsn_mark->lock);
- if (!(oldmask & ~mask))
+ if (destroy_mark)
fsnotify_destroy_mark(fsn_mark);
return mask & oldmask;
@@ -539,17 +544,23 @@ static int fanotify_remove_vfsmount_mark(struct fsnotify_group *group,
{
struct fsnotify_mark *fsn_mark = NULL;
__u32 removed;
+ int ret;
+ mutex_lock(&group->mutex);
+ ret = -ENOENT;
fsn_mark = fsnotify_find_vfsmount_mark(group, mnt);
if (!fsn_mark)
- return -ENOENT;
+ goto err;
removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags);
fsnotify_put_mark(fsn_mark);
if (removed & mnt->mnt_fsnotify_mask)
fsnotify_recalc_vfsmount_mask(mnt);
+ ret = 0;
+err:
+ mutex_unlock(&group->mutex);
- return 0;
+ return ret;
}
static int fanotify_remove_inode_mark(struct fsnotify_group *group,
@@ -558,18 +569,24 @@ static int fanotify_remove_inode_mark(struct fsnotify_group *group,
{
struct fsnotify_mark *fsn_mark = NULL;
__u32 removed;
+ int ret;
+ mutex_lock(&group->mutex);
+ ret = -ENOENT;
fsn_mark = fsnotify_find_inode_mark(group, inode);
if (!fsn_mark)
- return -ENOENT;
+ goto err;
removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags);
/* matches the fsnotify_find_inode_mark() */
fsnotify_put_mark(fsn_mark);
if (removed & inode->i_fsnotify_mask)
fsnotify_recalc_inode_mask(inode);
+ ret = 0;
+err:
+ mutex_unlock(&group->mutex);
- return 0;
+ return ret;
}
static __u32 fanotify_mark_add_to_mask(struct fsnotify_mark *fsn_mark,
@@ -605,28 +622,35 @@ static int fanotify_add_vfsmount_mark(struct fsnotify_group *group,
{
struct fsnotify_mark *fsn_mark;
__u32 added;
- int ret = 0;
+ int ret;
+ mutex_lock(&group->mutex);
fsn_mark = fsnotify_find_vfsmount_mark(group, mnt);
if (!fsn_mark) {
- if (atomic_read(&group->num_marks) > group->fanotify_data.max_marks)
- return -ENOSPC;
+ ret = -ENOSPC;
+ if (atomic_read(&group->num_marks) >
+ group->fanotify_data.max_marks)
+ goto err;
+ ret = -ENOMEM;
fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
if (!fsn_mark)
- return -ENOMEM;
+ goto err;
fsnotify_init_mark(fsn_mark, fanotify_free_mark);
ret = fsnotify_add_mark(fsn_mark, group, NULL, mnt, 0);
if (ret)
- goto err;
+ goto err2;
}
added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
if (added & ~mnt->mnt_fsnotify_mask)
fsnotify_recalc_vfsmount_mask(mnt);
-err:
+ ret = 0;
+err2:
fsnotify_put_mark(fsn_mark);
+err:
+ mutex_unlock(&group->mutex);
return ret;
}
@@ -636,7 +660,7 @@ static int fanotify_add_inode_mark(struct fsnotify_group *group,
{
struct fsnotify_mark *fsn_mark;
__u32 added;
- int ret = 0;
+ int ret;
pr_debug("%s: group=%p inode=%p\n", __func__, group, inode);
@@ -650,26 +674,33 @@ static int fanotify_add_inode_mark(struct fsnotify_group *group,
(atomic_read(&inode->i_writecount) > 0))
return 0;
+ mutex_lock(&group->mutex);
fsn_mark = fsnotify_find_inode_mark(group, inode);
if (!fsn_mark) {
- if (atomic_read(&group->num_marks) > group->fanotify_data.max_marks)
- return -ENOSPC;
+ ret = -ENOSPC;
+ if (atomic_read(&group->num_marks) >
+ group->fanotify_data.max_marks)
+ goto err;
+ ret = -ENOMEM;
fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
if (!fsn_mark)
- return -ENOMEM;
+ goto err;
fsnotify_init_mark(fsn_mark, fanotify_free_mark);
ret = fsnotify_add_mark(fsn_mark, group, inode, NULL, 0);
if (ret)
- goto err;
+ goto err2;
}
added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
if (added & ~inode->i_fsnotify_mask)
fsnotify_recalc_inode_mask(inode);
-err:
+ ret = 0;
+err2:
fsnotify_put_mark(fsn_mark);
+err:
+ mutex_unlock(&group->mutex);
return ret;
}
@@ -711,7 +742,7 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
group->fanotify_data.user = user;
atomic_inc(&user->fanotify_listeners);
- group->fanotify_data.f_flags = event_f_flags;
+ group->fanotify_data.f_flags = event_f_flags | FMODE_NONOTIFY;
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
mutex_init(&group->fanotify_data.access_mutex);
init_waitqueue_head(&group->fanotify_data.access_waitq);
@@ -751,6 +782,14 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
group->fanotify_data.max_marks = FANOTIFY_DEFAULT_MAX_MARKS;
}
+ fd = -EINVAL;
+ if (flags & FAN_READONLY_FALLBACK) {
+ if ((event_f_flags & O_ACCMODE) == O_RDWR)
+ group->fanotify_data.readonly_fallback = true;
+ else
+ goto out_put_group;
+ }
+
fd = anon_inode_getfd("[fanotify]", &fanotify_fops, group, f_flags);
if (fd < 0)
goto out_put_group;
diff --git a/fs/notify/group.c b/fs/notify/group.c
index d309f38449cb..cc341d33f5c8 100644
--- a/fs/notify/group.c
+++ b/fs/notify/group.c
@@ -90,6 +90,7 @@ struct fsnotify_group *fsnotify_alloc_group(const struct fsnotify_ops *ops)
*/
atomic_set(&group->num_marks, 1);
+ mutex_init(&group->mutex);
mutex_init(&group->notification_mutex);
INIT_LIST_HEAD(&group->notification_list);
init_waitqueue_head(&group->notification_waitq);
diff --git a/fs/notify/mark.c b/fs/notify/mark.c
index 325185e514bb..28b64eb03e33 100644
--- a/fs/notify/mark.c
+++ b/fs/notify/mark.c
@@ -103,17 +103,6 @@ static DEFINE_SPINLOCK(destroy_lock);
static LIST_HEAD(destroy_list);
static DECLARE_WAIT_QUEUE_HEAD(destroy_waitq);
-void fsnotify_get_mark(struct fsnotify_mark *mark)
-{
- atomic_inc(&mark->refcnt);
-}
-
-void fsnotify_put_mark(struct fsnotify_mark *mark)
-{
- if (atomic_dec_and_test(&mark->refcnt))
- mark->free_mark(mark);
-}
-
/*
* Any time a mark is getting freed we end up here.
* The caller had better be holding a reference to this mark so we don't actually
@@ -217,7 +206,7 @@ int fsnotify_add_mark(struct fsnotify_mark *mark,
struct fsnotify_group *group, struct inode *inode,
struct vfsmount *mnt, int allow_dups)
{
- int ret = 0;
+ int ret;
BUG_ON(inode && mnt);
BUG_ON(!inode && !mnt);
@@ -232,23 +221,20 @@ int fsnotify_add_mark(struct fsnotify_mark *mark,
spin_lock(&group->mark_lock);
mark->flags |= FSNOTIFY_MARK_FLAG_ALIVE;
-
mark->group = group;
list_add(&mark->g_list, &group->marks_list);
- atomic_inc(&group->num_marks);
fsnotify_get_mark(mark); /* for i_list and g_list */
+ atomic_inc(&group->num_marks);
- if (inode) {
+ ret = 0;
+ if (inode)
ret = fsnotify_add_inode_mark(mark, group, inode, allow_dups);
- if (ret)
- goto err;
- } else if (mnt) {
+ else if (mnt)
ret = fsnotify_add_vfsmount_mark(mark, group, mnt, allow_dups);
- if (ret)
- goto err;
- } else {
+ else
BUG();
- }
+ if (ret)
+ goto err;
spin_unlock(&group->mark_lock);
@@ -260,7 +246,7 @@ int fsnotify_add_mark(struct fsnotify_mark *mark,
if (inode)
__fsnotify_update_child_dentry_flags(inode);
- return ret;
+ return 0;
err:
mark->flags &= ~FSNOTIFY_MARK_FLAG_ALIVE;
list_del_init(&mark->g_list);
@@ -346,6 +332,10 @@ static int fsnotify_mark_destroy(void *ignored)
synchronize_srcu(&fsnotify_mark_srcu);
+ /*
+ * at this point we cannot be found via the i_list or g_list so
+ * drop that reference.
+ */
list_for_each_entry_safe(mark, next, &private_destroy_list, destroy_list) {
list_del_init(&mark->destroy_list);
fsnotify_put_mark(mark);
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index 6c6133f76e16..b5fac2ba4a07 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -36,9 +36,12 @@
#define FAN_UNLIMITED_QUEUE 0x00000010
#define FAN_UNLIMITED_MARKS 0x00000020
+/* Attempt read-only open if read-write failed. */
+#define FAN_READONLY_FALLBACK 0x00000040
+
#define FAN_ALL_INIT_FLAGS (FAN_CLOEXEC | FAN_NONBLOCK | \
FAN_ALL_CLASS_BITS | FAN_UNLIMITED_QUEUE |\
- FAN_UNLIMITED_MARKS)
+ FAN_UNLIMITED_MARKS | FAN_READONLY_FALLBACK)
/* flags used for fanotify_modify_mark() */
#define FAN_MARK_ADD 0x00000001
diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h
index 69ad89b50489..8aa4731c9f6f 100644
--- a/include/linux/fsnotify_backend.h
+++ b/include/linux/fsnotify_backend.h
@@ -125,6 +125,7 @@ struct fsnotify_group {
const struct fsnotify_ops *ops; /* how this group handles things */
+ struct mutex mutex;
/* needed to send notification to userspace */
struct mutex notification_mutex; /* protect the notification_list */
struct list_head notification_list; /* list of event_holder this group needs to send to userspace */
@@ -168,6 +169,7 @@ struct fsnotify_group {
wait_queue_head_t access_waitq;
atomic_t bypass_perm;
#endif /* CONFIG_FANOTIFY_ACCESS_PERMISSIONS */
+ bool readonly_fallback;
int f_flags;
unsigned int max_marks;
struct user_struct *user;
@@ -415,8 +417,6 @@ extern void fsnotify_clear_inode_marks_by_group(struct fsnotify_group *group);
extern void fsnotify_clear_marks_by_group_flags(struct fsnotify_group *group, unsigned int flags);
/* run all the marks in a group, and flag them to be freed */
extern void fsnotify_clear_marks_by_group(struct fsnotify_group *group);
-extern void fsnotify_get_mark(struct fsnotify_mark *mark);
-extern void fsnotify_put_mark(struct fsnotify_mark *mark);
extern void fsnotify_unmount_inodes(struct list_head *list);
/* put here because inotify does some weird stuff when destroying watches */
@@ -430,6 +430,16 @@ extern struct fsnotify_event *fsnotify_clone_event(struct fsnotify_event *old_ev
extern int fsnotify_replace_event(struct fsnotify_event_holder *old_holder,
struct fsnotify_event *new_event);
+static inline void fsnotify_get_mark(struct fsnotify_mark *mark)
+{
+ atomic_inc(&mark->refcnt);
+}
+
+static inline void fsnotify_put_mark(struct fsnotify_mark *mark)
+{
+ if (atomic_dec_and_test(&mark->refcnt))
+ mark->free_mark(mark);
+}
#else
static inline int fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,