Skip to content

Commit

Permalink
Merge branch 'for-linus' of git://git.infradead.org/users/eparis/notify
Browse files Browse the repository at this point in the history
* 'for-linus' of git://git.infradead.org/users/eparis/notify: (22 commits)
  Ensure FMODE_NONOTIFY is not set by userspace
  make fanotify_read() restartable across signals
  fsnotify: remove alignment padding from fsnotify_mark on 64 bit builds
  fs/notify/fanotify/fanotify_user.c: fix warnings
  fanotify: Fix FAN_CLOSE comments
  fanotify: do not recalculate the mask if the ignored mask changed
  fanotify: ignore events on directories unless specifically requested
  fsnotify: rename FS_IN_ISDIR to FS_ISDIR
  fanotify: do not send events for irregular files
  fanotify: limit number of listeners per user
  fanotify: allow userspace to override max marks
  fanotify: limit the number of marks in a single fanotify group
  fanotify: allow userspace to override max queue depth
  fsnotify: implement a default maximum queue depth
  fanotify: ignore fanotify ignore marks if open writers
  fanotify: allow userspace to flush all marks
  fsnotify: call fsnotify_parent in perm events
  fsnotify: correctly handle return codes from listeners
  fanotify: use __aligned_u64 in fanotify userspace metadata
  fanotify: implement fanotify listener ordering
  ...
  • Loading branch information
torvalds committed Oct 30, 2010
2 parents f02a38d + 6bff7ec commit 1792f17
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 64 deletions.
2 changes: 1 addition & 1 deletion fs/notify/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ config FSNOTIFY

source "fs/notify/dnotify/Kconfig"
source "fs/notify/inotify/Kconfig"
#source "fs/notify/fanotify/Kconfig"
source "fs/notify/fanotify/Kconfig"
27 changes: 21 additions & 6 deletions fs/notify/fanotify/fanotify.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ static int fanotify_handle_event(struct fsnotify_group *group,
BUILD_BUG_ON(FAN_Q_OVERFLOW != FS_Q_OVERFLOW);
BUILD_BUG_ON(FAN_OPEN_PERM != FS_OPEN_PERM);
BUILD_BUG_ON(FAN_ACCESS_PERM != FS_ACCESS_PERM);
BUILD_BUG_ON(FAN_ONDIR != FS_ISDIR);

pr_debug("%s: group=%p event=%p\n", __func__, group, event);

Expand Down Expand Up @@ -160,20 +161,21 @@ static bool fanotify_should_send_event(struct fsnotify_group *group,
__u32 event_mask, void *data, int data_type)
{
__u32 marks_mask, marks_ignored_mask;
struct path *path = data;

pr_debug("%s: group=%p to_tell=%p inode_mark=%p vfsmnt_mark=%p "
"mask=%x data=%p data_type=%d\n", __func__, group, to_tell,
inode_mark, vfsmnt_mark, event_mask, data, data_type);

/* sorry, fanotify only gives a damn about files and dirs */
if (!S_ISREG(to_tell->i_mode) &&
!S_ISDIR(to_tell->i_mode))
return false;

/* if we don't have enough info to send an event to userspace say no */
if (data_type != FSNOTIFY_EVENT_PATH)
return false;

/* sorry, fanotify only gives a damn about files and dirs */
if (!S_ISREG(path->dentry->d_inode->i_mode) &&
!S_ISDIR(path->dentry->d_inode->i_mode))
return false;

if (inode_mark && vfsmnt_mark) {
marks_mask = (vfsmnt_mark->mask | inode_mark->mask);
marks_ignored_mask = (vfsmnt_mark->ignored_mask | inode_mark->ignored_mask);
Expand All @@ -194,16 +196,29 @@ static bool fanotify_should_send_event(struct fsnotify_group *group,
BUG();
}

if (S_ISDIR(path->dentry->d_inode->i_mode) &&
(marks_ignored_mask & FS_ISDIR))
return false;

if (event_mask & marks_mask & ~marks_ignored_mask)
return true;

return false;
}

static void fanotify_free_group_priv(struct fsnotify_group *group)
{
struct user_struct *user;

user = group->fanotify_data.user;
atomic_dec(&user->fanotify_listeners);
free_uid(user);
}

const struct fsnotify_ops fanotify_fsnotify_ops = {
.handle_event = fanotify_handle_event,
.should_send_event = fanotify_should_send_event,
.free_group_priv = NULL,
.free_group_priv = fanotify_free_group_priv,
.free_event_priv = NULL,
.freeing_mark = NULL,
};
98 changes: 90 additions & 8 deletions fs/notify/fanotify/fanotify_user.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

#include <asm/ioctls.h>

#define FANOTIFY_DEFAULT_MAX_EVENTS 16384
#define FANOTIFY_DEFAULT_MAX_MARKS 8192
#define FANOTIFY_DEFAULT_MAX_LISTENERS 128

extern const struct fsnotify_ops fanotify_fsnotify_ops;

static struct kmem_cache *fanotify_mark_cache __read_mostly;
Expand Down Expand Up @@ -326,7 +330,7 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
ret = -EAGAIN;
if (file->f_flags & O_NONBLOCK)
break;
ret = -EINTR;
ret = -ERESTARTSYS;
if (signal_pending(current))
break;

Expand Down Expand Up @@ -372,11 +376,10 @@ static ssize_t fanotify_write(struct file *file, const char __user *buf, size_t
static int fanotify_release(struct inode *ignored, struct file *file)
{
struct fsnotify_group *group = file->private_data;
struct fanotify_response_event *re, *lre;

pr_debug("%s: file=%p group=%p\n", __func__, file, group);

#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
struct fanotify_response_event *re, *lre;

mutex_lock(&group->fanotify_data.access_mutex);

group->fanotify_data.bypass_perm = true;
Expand Down Expand Up @@ -554,18 +557,24 @@ static __u32 fanotify_mark_add_to_mask(struct fsnotify_mark *fsn_mark,
__u32 mask,
unsigned int flags)
{
__u32 oldmask;
__u32 oldmask = -1;

spin_lock(&fsn_mark->lock);
if (!(flags & FAN_MARK_IGNORED_MASK)) {
oldmask = fsn_mark->mask;
fsnotify_set_mark_mask_locked(fsn_mark, (oldmask | mask));
} else {
oldmask = fsn_mark->ignored_mask;
fsnotify_set_mark_ignored_mask_locked(fsn_mark, (oldmask | mask));
__u32 tmask = fsn_mark->ignored_mask | mask;
fsnotify_set_mark_ignored_mask_locked(fsn_mark, tmask);
if (flags & FAN_MARK_IGNORED_SURV_MODIFY)
fsn_mark->flags |= FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY;
}

if (!(flags & FAN_MARK_ONDIR)) {
__u32 tmask = fsn_mark->ignored_mask | FAN_ONDIR;
fsnotify_set_mark_ignored_mask_locked(fsn_mark, tmask);
}

spin_unlock(&fsn_mark->lock);

return mask & ~oldmask;
Expand All @@ -582,6 +591,9 @@ static int fanotify_add_vfsmount_mark(struct fsnotify_group *group,
if (!fsn_mark) {
int ret;

if (atomic_read(&group->num_marks) > group->fanotify_data.max_marks)
return -ENOSPC;

fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
if (!fsn_mark)
return -ENOMEM;
Expand Down Expand Up @@ -610,10 +622,23 @@ static int fanotify_add_inode_mark(struct fsnotify_group *group,

pr_debug("%s: group=%p inode=%p\n", __func__, group, inode);

/*
* If some other task has this inode open for write we should not add
* an ignored mark, unless that ignored mark is supposed to survive
* modification changes anyway.
*/
if ((flags & FAN_MARK_IGNORED_MASK) &&
!(flags & FAN_MARK_IGNORED_SURV_MODIFY) &&
(atomic_read(&inode->i_writecount) > 0))
return 0;

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;

fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
if (!fsn_mark)
return -ENOMEM;
Expand All @@ -637,6 +662,7 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
{
struct fsnotify_group *group;
int f_flags, fd;
struct user_struct *user;

pr_debug("%s: flags=%d event_f_flags=%d\n",
__func__, flags, event_f_flags);
Expand All @@ -647,6 +673,12 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
if (flags & ~FAN_ALL_INIT_FLAGS)
return -EINVAL;

user = get_current_user();
if (atomic_read(&user->fanotify_listeners) > FANOTIFY_DEFAULT_MAX_LISTENERS) {
free_uid(user);
return -EMFILE;
}

f_flags = O_RDWR | FMODE_NONOTIFY;
if (flags & FAN_CLOEXEC)
f_flags |= O_CLOEXEC;
Expand All @@ -658,12 +690,47 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
if (IS_ERR(group))
return PTR_ERR(group);

group->fanotify_data.user = user;
atomic_inc(&user->fanotify_listeners);

group->fanotify_data.f_flags = event_f_flags;
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
mutex_init(&group->fanotify_data.access_mutex);
init_waitqueue_head(&group->fanotify_data.access_waitq);
INIT_LIST_HEAD(&group->fanotify_data.access_list);
#endif
switch (flags & FAN_ALL_CLASS_BITS) {
case FAN_CLASS_NOTIF:
group->priority = FS_PRIO_0;
break;
case FAN_CLASS_CONTENT:
group->priority = FS_PRIO_1;
break;
case FAN_CLASS_PRE_CONTENT:
group->priority = FS_PRIO_2;
break;
default:
fd = -EINVAL;
goto out_put_group;
}

if (flags & FAN_UNLIMITED_QUEUE) {
fd = -EPERM;
if (!capable(CAP_SYS_ADMIN))
goto out_put_group;
group->max_events = UINT_MAX;
} else {
group->max_events = FANOTIFY_DEFAULT_MAX_EVENTS;
}

if (flags & FAN_UNLIMITED_MARKS) {
fd = -EPERM;
if (!capable(CAP_SYS_ADMIN))
goto out_put_group;
group->fanotify_data.max_marks = UINT_MAX;
} else {
group->fanotify_data.max_marks = FANOTIFY_DEFAULT_MAX_MARKS;
}

fd = anon_inode_getfd("[fanotify]", &fanotify_fops, group, f_flags);
if (fd < 0)
Expand Down Expand Up @@ -704,6 +771,12 @@ SYSCALL_DEFINE(fanotify_mark)(int fanotify_fd, unsigned int flags,
default:
return -EINVAL;
}

if (mask & FAN_ONDIR) {
flags |= FAN_MARK_ONDIR;
mask &= ~FAN_ONDIR;
}

#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
if (mask & ~(FAN_ALL_EVENTS | FAN_ALL_PERM_EVENTS | FAN_EVENT_ON_CHILD))
#else
Expand All @@ -719,6 +792,16 @@ SYSCALL_DEFINE(fanotify_mark)(int fanotify_fd, unsigned int flags,
ret = -EINVAL;
if (unlikely(filp->f_op != &fanotify_fops))
goto fput_and_out;
group = filp->private_data;

/*
* group->priority == FS_PRIO_0 == FAN_CLASS_NOTIF. These are not
* allowed to set permissions events.
*/
ret = -EINVAL;
if (mask & FAN_ALL_PERM_EVENTS &&
group->priority == FS_PRIO_0)
goto fput_and_out;

ret = fanotify_find_path(dfd, pathname, &path, flags);
if (ret)
Expand All @@ -729,7 +812,6 @@ SYSCALL_DEFINE(fanotify_mark)(int fanotify_fd, unsigned int flags,
inode = path.dentry->d_inode;
else
mnt = path.mnt;
group = filp->private_data;

/* create/update an inode mark */
switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE | FAN_MARK_FLUSH)) {
Expand Down
35 changes: 21 additions & 14 deletions fs/notify/fsnotify.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,17 @@ void __fsnotify_update_child_dentry_flags(struct inode *inode)
}

/* Notify this dentry's parent about a child's events. */
void __fsnotify_parent(struct path *path, struct dentry *dentry, __u32 mask)
int __fsnotify_parent(struct path *path, struct dentry *dentry, __u32 mask)
{
struct dentry *parent;
struct inode *p_inode;
int ret = 0;

if (!dentry)
dentry = path->dentry;

if (!(dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED))
return;
return 0;

parent = dget_parent(dentry);
p_inode = parent->d_inode;
Expand All @@ -106,14 +107,16 @@ void __fsnotify_parent(struct path *path, struct dentry *dentry, __u32 mask)
mask |= FS_EVENT_ON_CHILD;

if (path)
fsnotify(p_inode, mask, path, FSNOTIFY_EVENT_PATH,
dentry->d_name.name, 0);
ret = fsnotify(p_inode, mask, path, FSNOTIFY_EVENT_PATH,
dentry->d_name.name, 0);
else
fsnotify(p_inode, mask, dentry->d_inode, FSNOTIFY_EVENT_INODE,
dentry->d_name.name, 0);
ret = fsnotify(p_inode, mask, dentry->d_inode, FSNOTIFY_EVENT_INODE,
dentry->d_name.name, 0);
}

dput(parent);

return ret;
}
EXPORT_SYMBOL_GPL(__fsnotify_parent);

Expand Down Expand Up @@ -252,28 +255,32 @@ int fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,

if (inode_group > vfsmount_group) {
/* handle inode */
send_to_group(to_tell, NULL, inode_mark, NULL, mask, data,
data_is, cookie, file_name, &event);
ret = send_to_group(to_tell, NULL, inode_mark, NULL, mask, data,
data_is, cookie, file_name, &event);
/* we didn't use the vfsmount_mark */
vfsmount_group = NULL;
} else if (vfsmount_group > inode_group) {
send_to_group(to_tell, mnt, NULL, vfsmount_mark, mask, data,
data_is, cookie, file_name, &event);
ret = send_to_group(to_tell, mnt, NULL, vfsmount_mark, mask, data,
data_is, cookie, file_name, &event);
inode_group = NULL;
} else {
send_to_group(to_tell, mnt, inode_mark, vfsmount_mark,
mask, data, data_is, cookie, file_name,
&event);
ret = send_to_group(to_tell, mnt, inode_mark, vfsmount_mark,
mask, data, data_is, cookie, file_name,
&event);
}

if (ret && (mask & ALL_FSNOTIFY_PERM_EVENTS))
goto out;

if (inode_group)
inode_node = srcu_dereference(inode_node->next,
&fsnotify_mark_srcu);
if (vfsmount_group)
vfsmount_node = srcu_dereference(vfsmount_node->next,
&fsnotify_mark_srcu);
}

ret = 0;
out:
srcu_read_unlock(&fsnotify_mark_srcu, idx);
/*
* fsnotify_create_event() took a reference so the event can't be cleaned
Expand Down
9 changes: 7 additions & 2 deletions fs/notify/inode_mark.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ void fsnotify_set_inode_mark_mask_locked(struct fsnotify_mark *mark,
* Attach an initialized mark to a given inode.
* These marks may be used for the fsnotify backend to determine which
* event types should be delivered to which group and for which inodes. These
* marks are ordered according to the group's location in memory.
* marks are ordered according to priority, highest number first, and then by
* the group's location in memory.
*/
int fsnotify_add_inode_mark(struct fsnotify_mark *mark,
struct fsnotify_group *group, struct inode *inode,
Expand Down Expand Up @@ -211,7 +212,11 @@ int fsnotify_add_inode_mark(struct fsnotify_mark *mark,
goto out;
}

if (mark->group < lmark->group)
if (mark->group->priority < lmark->group->priority)
continue;

if ((mark->group->priority == lmark->group->priority) &&
(mark->group < lmark->group))
continue;

hlist_add_before_rcu(&mark->i.i_list, &lmark->i.i_list);
Expand Down
2 changes: 1 addition & 1 deletion fs/notify/inotify/inotify_user.c
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,7 @@ static int __init inotify_user_setup(void)
BUILD_BUG_ON(IN_Q_OVERFLOW != FS_Q_OVERFLOW);
BUILD_BUG_ON(IN_IGNORED != FS_IN_IGNORED);
BUILD_BUG_ON(IN_EXCL_UNLINK != FS_EXCL_UNLINK);
BUILD_BUG_ON(IN_ISDIR != FS_IN_ISDIR);
BUILD_BUG_ON(IN_ISDIR != FS_ISDIR);
BUILD_BUG_ON(IN_ONESHOT != FS_IN_ONESHOT);

BUG_ON(hweight32(ALL_INOTIFY_BITS) != 21);
Expand Down
Loading

0 comments on commit 1792f17

Please sign in to comment.