summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Rothwell <sfr@canb.auug.org.au>2010-11-22 12:14:39 +1100
committerStephen Rothwell <sfr@canb.auug.org.au>2010-11-22 12:14:39 +1100
commit3f1a18692db1dc08bc5e3166c6185b7f9f31ed5b (patch)
treefc602f6c6e2b8b7c6d5a8e9d09df96d7a2e57213
parent73d968d929f88dc47c89f2febd85a5a4d8719611 (diff)
parent3aa13e3ff6700929c0e3a1a4cdc51c82139707e4 (diff)
Merge remote branch 'fsnotify/for-next'
-rw-r--r--fs/namei.c3
-rw-r--r--fs/notify/fanotify/fanotify.c24
-rw-r--r--fs/notify/fanotify/fanotify_user.c78
-rw-r--r--fs/notify/mark.c36
-rw-r--r--include/linux/fanotify.h7
-rw-r--r--include/linux/fsnotify.h3
-rw-r--r--include/linux/fsnotify_backend.h13
7 files changed, 97 insertions, 67 deletions
diff --git a/fs/namei.c b/fs/namei.c
index 5362af9b7372..4ff7ca530533 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1748,6 +1748,9 @@ struct file *do_filp_open(int dfd, const char *pathname,
if (!(open_flag & O_CREAT))
mode = 0;
+ /* Must never be set by userspace */
+ open_flag &= ~FMODE_NONOTIFY;
+
/*
* O_SYNC is implemented as __O_SYNC|O_DSYNC. As many places only
* check for O_DSYNC if the need any syncing at all we enforce it's
diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index b04f88eed09e..cb576b84e9ea 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -180,13 +180,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) {
@@ -200,10 +193,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 063224812b7e..ae36c73cc7c5 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
@@ -106,7 +117,7 @@ static int create_fd(struct fsnotify_group *group, struct fsnotify_event *event)
return client_fd;
}
-static ssize_t fill_event_metadata(struct fsnotify_group *group,
+static int fill_event_metadata(struct fsnotify_group *group,
struct fanotify_event_metadata *metadata,
struct fsnotify_event *event)
{
@@ -257,10 +268,11 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
- fd = fill_event_metadata(group, &fanotify_event_metadata, event);
- if (fd < 0)
- return fd;
+ ret = fill_event_metadata(group, &fanotify_event_metadata, event);
+ if (ret < 0)
+ goto out;
+ fd = ret;
ret = prepare_for_access_response(group, event, fd);
if (ret)
goto out_close_fd;
@@ -275,6 +287,13 @@ out_kill_access_response:
remove_access_response(group, event, fd);
out_close_fd:
sys_close(fd);
+out:
+#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
+ if (event->mask & FAN_ALL_PERM_EVENTS) {
+ event->response = FAN_DENY;
+ wake_up(&group->fanotify_data.access_waitq);
+ }
+#endif
return ret;
}
@@ -586,11 +605,10 @@ static int fanotify_add_vfsmount_mark(struct fsnotify_group *group,
{
struct fsnotify_mark *fsn_mark;
__u32 added;
+ int ret = 0;
fsn_mark = fsnotify_find_vfsmount_mark(group, mnt);
if (!fsn_mark) {
- int ret;
-
if (atomic_read(&group->num_marks) > group->fanotify_data.max_marks)
return -ENOSPC;
@@ -600,17 +618,16 @@ static int fanotify_add_vfsmount_mark(struct fsnotify_group *group,
fsnotify_init_mark(fsn_mark, fanotify_free_mark);
ret = fsnotify_add_mark(fsn_mark, group, NULL, mnt, 0);
- if (ret) {
- fanotify_free_mark(fsn_mark);
- return ret;
- }
+ if (ret)
+ goto err;
}
added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
- fsnotify_put_mark(fsn_mark);
+
if (added & ~mnt->mnt_fsnotify_mask)
fsnotify_recalc_vfsmount_mask(mnt);
-
- return 0;
+err:
+ fsnotify_put_mark(fsn_mark);
+ return ret;
}
static int fanotify_add_inode_mark(struct fsnotify_group *group,
@@ -619,6 +636,7 @@ static int fanotify_add_inode_mark(struct fsnotify_group *group,
{
struct fsnotify_mark *fsn_mark;
__u32 added;
+ int ret = 0;
pr_debug("%s: group=%p inode=%p\n", __func__, group, inode);
@@ -634,8 +652,6 @@ static int fanotify_add_inode_mark(struct fsnotify_group *group,
fsn_mark = fsnotify_find_inode_mark(group, inode);
if (!fsn_mark) {
- int ret;
-
if (atomic_read(&group->num_marks) > group->fanotify_data.max_marks)
return -ENOSPC;
@@ -645,16 +661,16 @@ static int fanotify_add_inode_mark(struct fsnotify_group *group,
fsnotify_init_mark(fsn_mark, fanotify_free_mark);
ret = fsnotify_add_mark(fsn_mark, group, inode, NULL, 0);
- if (ret) {
- fanotify_free_mark(fsn_mark);
- return ret;
- }
+ if (ret)
+ goto err;
}
added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
- fsnotify_put_mark(fsn_mark);
+
if (added & ~inode->i_fsnotify_mask)
fsnotify_recalc_inode_mask(inode);
- return 0;
+err:
+ fsnotify_put_mark(fsn_mark);
+ return ret;
}
/* fanotify syscalls */
@@ -693,7 +709,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);
@@ -732,6 +748,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/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 0f0121467fc4..3db66ef817ee 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
@@ -96,7 +99,7 @@ struct fanotify_event_metadata {
struct fanotify_response {
__s32 fd;
__u32 response;
-} __attribute__ ((packed));
+};
/* Legit userspace responses to a _PERM event */
#define FAN_ALLOW 0x01
diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h
index 5c185fa27089..b10bcdeaef76 100644
--- a/include/linux/fsnotify.h
+++ b/include/linux/fsnotify.h
@@ -235,9 +235,6 @@ static inline void fsnotify_open(struct file *file)
if (S_ISDIR(inode->i_mode))
mask |= FS_ISDIR;
- /* FMODE_NONOTIFY must never be set from user */
- file->f_mode &= ~FMODE_NONOTIFY;
-
fsnotify_parent(path, NULL, mask);
fsnotify(inode, mask, path, FSNOTIFY_EVENT_PATH, NULL, 0);
}
diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h
index 0a68f924f06f..d010f70c3b6c 100644
--- a/include/linux/fsnotify_backend.h
+++ b/include/linux/fsnotify_backend.h
@@ -168,6 +168,7 @@ struct fsnotify_group {
wait_queue_head_t access_waitq;
bool bypass_perm; /* protected by access_mutex */
#endif /* CONFIG_FANOTIFY_ACCESS_PERMISSIONS */
+ bool readonly_fallback;
int f_flags;
unsigned int max_marks;
struct user_struct *user;
@@ -412,8 +413,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 */
@@ -427,6 +426,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,