diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2008-12-31 15:57:56 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-12-31 15:57:56 -0800 |
commit | b58602a4bac012b5f4fc12fe6b46ab237b610d5d (patch) | |
tree | 38929e210a723f67fe2b945f37264bf3aae3c2c4 /fs/notify/dnotify/dnotify.c | |
parent | db200df0b3530f673d8e9f5bd535e9e10305842a (diff) | |
parent | 1f3403fa640f9f7b135dee79f2d39d01c8ad4a08 (diff) |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs-2.6
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs-2.6: (34 commits)
nfsd race fixes: jfs
nfsd race fixes: reiserfs
nfsd race fixes: ext4
nfsd race fixes: ext3
nfsd race fixes: ext2
nfsd/create race fixes, infrastructure
filesystem notification: create fs/notify to contain all fs notification
fs/block_dev.c: __read_mostly improvement and sb_is_blkdev_sb utilization
kill ->dir_notify()
filp_cachep can be static in fs/file_table.c
fix f_count description in Documentation/filesystems/files.txt
make INIT_FS use the __RW_LOCK_UNLOCKED initialization
take init_fs to saner place
kill vfs_permission
pass a struct path * to may_open
kill walk_init_root
remove incorrect comment in inode_permission
expand some comments (d_path / seq_path)
correct wrong function name of d_put in kernel document and source comment
fix switch_names() breakage in short-to-short case
...
Diffstat (limited to 'fs/notify/dnotify/dnotify.c')
-rw-r--r-- | fs/notify/dnotify/dnotify.c | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/fs/notify/dnotify/dnotify.c b/fs/notify/dnotify/dnotify.c new file mode 100644 index 000000000000..b0aa2cde80bd --- /dev/null +++ b/fs/notify/dnotify/dnotify.c @@ -0,0 +1,191 @@ +/* + * Directory notifications for Linux. + * + * Copyright (C) 2000,2001,2002 Stephen Rothwell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/dnotify.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/fdtable.h> + +int dir_notify_enable __read_mostly = 1; + +static struct kmem_cache *dn_cache __read_mostly; + +static void redo_inode_mask(struct inode *inode) +{ + unsigned long new_mask; + struct dnotify_struct *dn; + + new_mask = 0; + for (dn = inode->i_dnotify; dn != NULL; dn = dn->dn_next) + new_mask |= dn->dn_mask & ~DN_MULTISHOT; + inode->i_dnotify_mask = new_mask; +} + +void dnotify_flush(struct file *filp, fl_owner_t id) +{ + struct dnotify_struct *dn; + struct dnotify_struct **prev; + struct inode *inode; + + inode = filp->f_path.dentry->d_inode; + if (!S_ISDIR(inode->i_mode)) + return; + spin_lock(&inode->i_lock); + prev = &inode->i_dnotify; + while ((dn = *prev) != NULL) { + if ((dn->dn_owner == id) && (dn->dn_filp == filp)) { + *prev = dn->dn_next; + redo_inode_mask(inode); + kmem_cache_free(dn_cache, dn); + break; + } + prev = &dn->dn_next; + } + spin_unlock(&inode->i_lock); +} + +int fcntl_dirnotify(int fd, struct file *filp, unsigned long arg) +{ + struct dnotify_struct *dn; + struct dnotify_struct *odn; + struct dnotify_struct **prev; + struct inode *inode; + fl_owner_t id = current->files; + struct file *f; + int error = 0; + + if ((arg & ~DN_MULTISHOT) == 0) { + dnotify_flush(filp, id); + return 0; + } + if (!dir_notify_enable) + return -EINVAL; + inode = filp->f_path.dentry->d_inode; + if (!S_ISDIR(inode->i_mode)) + return -ENOTDIR; + dn = kmem_cache_alloc(dn_cache, GFP_KERNEL); + if (dn == NULL) + return -ENOMEM; + spin_lock(&inode->i_lock); + prev = &inode->i_dnotify; + while ((odn = *prev) != NULL) { + if ((odn->dn_owner == id) && (odn->dn_filp == filp)) { + odn->dn_fd = fd; + odn->dn_mask |= arg; + inode->i_dnotify_mask |= arg & ~DN_MULTISHOT; + goto out_free; + } + prev = &odn->dn_next; + } + + rcu_read_lock(); + f = fcheck(fd); + rcu_read_unlock(); + /* we'd lost the race with close(), sod off silently */ + /* note that inode->i_lock prevents reordering problems + * between accesses to descriptor table and ->i_dnotify */ + if (f != filp) + goto out_free; + + error = __f_setown(filp, task_pid(current), PIDTYPE_PID, 0); + if (error) + goto out_free; + + dn->dn_mask = arg; + dn->dn_fd = fd; + dn->dn_filp = filp; + dn->dn_owner = id; + inode->i_dnotify_mask |= arg & ~DN_MULTISHOT; + dn->dn_next = inode->i_dnotify; + inode->i_dnotify = dn; + spin_unlock(&inode->i_lock); + return 0; + +out_free: + spin_unlock(&inode->i_lock); + kmem_cache_free(dn_cache, dn); + return error; +} + +void __inode_dir_notify(struct inode *inode, unsigned long event) +{ + struct dnotify_struct * dn; + struct dnotify_struct **prev; + struct fown_struct * fown; + int changed = 0; + + spin_lock(&inode->i_lock); + prev = &inode->i_dnotify; + while ((dn = *prev) != NULL) { + if ((dn->dn_mask & event) == 0) { + prev = &dn->dn_next; + continue; + } + fown = &dn->dn_filp->f_owner; + send_sigio(fown, dn->dn_fd, POLL_MSG); + if (dn->dn_mask & DN_MULTISHOT) + prev = &dn->dn_next; + else { + *prev = dn->dn_next; + changed = 1; + kmem_cache_free(dn_cache, dn); + } + } + if (changed) + redo_inode_mask(inode); + spin_unlock(&inode->i_lock); +} + +EXPORT_SYMBOL(__inode_dir_notify); + +/* + * This is hopelessly wrong, but unfixable without API changes. At + * least it doesn't oops the kernel... + * + * To safely access ->d_parent we need to keep d_move away from it. Use the + * dentry's d_lock for this. + */ +void dnotify_parent(struct dentry *dentry, unsigned long event) +{ + struct dentry *parent; + + if (!dir_notify_enable) + return; + + spin_lock(&dentry->d_lock); + parent = dentry->d_parent; + if (parent->d_inode->i_dnotify_mask & event) { + dget(parent); + spin_unlock(&dentry->d_lock); + __inode_dir_notify(parent->d_inode, event); + dput(parent); + } else { + spin_unlock(&dentry->d_lock); + } +} +EXPORT_SYMBOL_GPL(dnotify_parent); + +static int __init dnotify_init(void) +{ + dn_cache = kmem_cache_create("dnotify_cache", + sizeof(struct dnotify_struct), 0, SLAB_PANIC, NULL); + return 0; +} + +module_init(dnotify_init) |