From 3a93e17cf68b01fa29c7a2e861ce508dcd3401ca Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 15 Sep 2013 19:41:16 -0400 Subject: ecryptfs: check DCACHE_OP_REVALIDATE instead of ->d_op Signed-off-by: Al Viro --- fs/ecryptfs/dentry.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'fs/ecryptfs') diff --git a/fs/ecryptfs/dentry.c b/fs/ecryptfs/dentry.c index bf12ba5dd223..46e31c92e574 100644 --- a/fs/ecryptfs/dentry.c +++ b/fs/ecryptfs/dentry.c @@ -51,7 +51,7 @@ static int ecryptfs_d_revalidate(struct dentry *dentry, unsigned int flags) return -ECHILD; lower_dentry = ecryptfs_dentry_to_lower(dentry); - if (!lower_dentry->d_op || !lower_dentry->d_op->d_revalidate) + if (!(lower_dentry->d_flags & DCACHE_OP_REVALIDATE)) goto out; rc = lower_dentry->d_op->d_revalidate(lower_dentry, flags); if (dentry->d_inode) { -- cgit v1.2.3-59-g8ed1b From 2edbfbf1c1ab0aeb58ce8abfb69b6f6b63542848 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 15 Sep 2013 20:45:11 -0400 Subject: ecryptfs: don't leave RCU pathwalk immediately If the underlying dentry doesn't have ->d_revalidate(), there's no need to force dropping out of RCU mode. All we need for that is to make freeing ecryptfs_dentry_info RCU-delayed. Signed-off-by: Al Viro --- fs/ecryptfs/dentry.c | 30 ++++++++++++++++-------------- fs/ecryptfs/ecryptfs_kernel.h | 5 ++++- 2 files changed, 20 insertions(+), 15 deletions(-) (limited to 'fs/ecryptfs') diff --git a/fs/ecryptfs/dentry.c b/fs/ecryptfs/dentry.c index 46e31c92e574..8cefa8e13bcc 100644 --- a/fs/ecryptfs/dentry.c +++ b/fs/ecryptfs/dentry.c @@ -44,15 +44,15 @@ */ static int ecryptfs_d_revalidate(struct dentry *dentry, unsigned int flags) { - struct dentry *lower_dentry; - int rc = 1; + struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry); + int rc; + + if (!(lower_dentry->d_flags & DCACHE_OP_REVALIDATE)) + return 1; if (flags & LOOKUP_RCU) return -ECHILD; - lower_dentry = ecryptfs_dentry_to_lower(dentry); - if (!(lower_dentry->d_flags & DCACHE_OP_REVALIDATE)) - goto out; rc = lower_dentry->d_op->d_revalidate(lower_dentry, flags); if (dentry->d_inode) { struct inode *lower_inode = @@ -60,12 +60,17 @@ static int ecryptfs_d_revalidate(struct dentry *dentry, unsigned int flags) fsstack_copy_attr_all(dentry->d_inode, lower_inode); } -out: return rc; } struct kmem_cache *ecryptfs_dentry_info_cache; +static void ecryptfs_dentry_free_rcu(struct rcu_head *head) +{ + kmem_cache_free(ecryptfs_dentry_info_cache, + container_of(head, struct ecryptfs_dentry_info, rcu)); +} + /** * ecryptfs_d_release * @dentry: The ecryptfs dentry @@ -74,15 +79,12 @@ struct kmem_cache *ecryptfs_dentry_info_cache; */ static void ecryptfs_d_release(struct dentry *dentry) { - if (ecryptfs_dentry_to_private(dentry)) { - if (ecryptfs_dentry_to_lower(dentry)) { - dput(ecryptfs_dentry_to_lower(dentry)); - mntput(ecryptfs_dentry_to_lower_mnt(dentry)); - } - kmem_cache_free(ecryptfs_dentry_info_cache, - ecryptfs_dentry_to_private(dentry)); + struct ecryptfs_dentry_info *p = dentry->d_fsdata; + if (p) { + if (p->lower_path.dentry) + path_put(&p->lower_path); + call_rcu(&p->rcu, ecryptfs_dentry_free_rcu); } - return; } const struct dentry_operations ecryptfs_dops = { diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h index df19d34a033b..e7ad2b3f24a7 100644 --- a/fs/ecryptfs/ecryptfs_kernel.h +++ b/fs/ecryptfs/ecryptfs_kernel.h @@ -261,7 +261,10 @@ struct ecryptfs_inode_info { * vfsmount too. */ struct ecryptfs_dentry_info { struct path lower_path; - struct ecryptfs_crypt_stat *crypt_stat; + union { + struct ecryptfs_crypt_stat *crypt_stat; + struct rcu_head rcu; + }; }; /** -- cgit v1.2.3-59-g8ed1b From 92dd123033d50051d1352fada1bad91c22a119cf Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 15 Sep 2013 20:50:13 -0400 Subject: ecryptfs: get rid of ecryptfs_set_dentry_lower{,_mnt} Signed-off-by: Al Viro --- fs/ecryptfs/ecryptfs_kernel.h | 14 -------------- fs/ecryptfs/inode.c | 4 ++-- fs/ecryptfs/main.c | 3 +-- 3 files changed, 3 insertions(+), 18 deletions(-) (limited to 'fs/ecryptfs') diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h index e7ad2b3f24a7..90d1882b306f 100644 --- a/fs/ecryptfs/ecryptfs_kernel.h +++ b/fs/ecryptfs/ecryptfs_kernel.h @@ -515,13 +515,6 @@ ecryptfs_dentry_to_lower(struct dentry *dentry) return ((struct ecryptfs_dentry_info *)dentry->d_fsdata)->lower_path.dentry; } -static inline void -ecryptfs_set_dentry_lower(struct dentry *dentry, struct dentry *lower_dentry) -{ - ((struct ecryptfs_dentry_info *)dentry->d_fsdata)->lower_path.dentry = - lower_dentry; -} - static inline struct vfsmount * ecryptfs_dentry_to_lower_mnt(struct dentry *dentry) { @@ -534,13 +527,6 @@ ecryptfs_dentry_to_lower_path(struct dentry *dentry) return &((struct ecryptfs_dentry_info *)dentry->d_fsdata)->lower_path; } -static inline void -ecryptfs_set_dentry_lower_mnt(struct dentry *dentry, struct vfsmount *lower_mnt) -{ - ((struct ecryptfs_dentry_info *)dentry->d_fsdata)->lower_path.mnt = - lower_mnt; -} - #define ecryptfs_printk(type, fmt, arg...) \ __ecryptfs_printk(type "%s: " fmt, __func__, ## arg); __printf(1, 2) diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 67e9b6339691..2ca62c40c6c2 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -361,8 +361,8 @@ static int ecryptfs_lookup_interpose(struct dentry *dentry, BUG_ON(!d_count(lower_dentry)); ecryptfs_set_dentry_private(dentry, dentry_info); - ecryptfs_set_dentry_lower(dentry, lower_dentry); - ecryptfs_set_dentry_lower_mnt(dentry, lower_mnt); + dentry_info->lower_path.mnt = lower_mnt; + dentry_info->lower_path.dentry = lower_dentry; if (!lower_dentry->d_inode) { /* We want to add because we couldn't find in lower */ diff --git a/fs/ecryptfs/main.c b/fs/ecryptfs/main.c index eb1c5979ecaf..1b119d3bf924 100644 --- a/fs/ecryptfs/main.c +++ b/fs/ecryptfs/main.c @@ -585,8 +585,7 @@ static struct dentry *ecryptfs_mount(struct file_system_type *fs_type, int flags /* ->kill_sb() will take care of root_info */ ecryptfs_set_dentry_private(s->s_root, root_info); - ecryptfs_set_dentry_lower(s->s_root, path.dentry); - ecryptfs_set_dentry_lower_mnt(s->s_root, path.mnt); + root_info->lower_path = path; s->s_flags |= MS_ACTIVE; return dget(s->s_root); -- cgit v1.2.3-59-g8ed1b From cbe9c08524a68a1bfc1ba8d84dabb985d18ff08d Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 15 Sep 2013 20:54:18 -0400 Subject: ecryptfs: ->lower_path.dentry is never NULL ... on anything found via ->d_fsdata Signed-off-by: Al Viro --- fs/ecryptfs/dentry.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'fs/ecryptfs') diff --git a/fs/ecryptfs/dentry.c b/fs/ecryptfs/dentry.c index 8cefa8e13bcc..4000f6b3a750 100644 --- a/fs/ecryptfs/dentry.c +++ b/fs/ecryptfs/dentry.c @@ -81,8 +81,7 @@ static void ecryptfs_d_release(struct dentry *dentry) { struct ecryptfs_dentry_info *p = dentry->d_fsdata; if (p) { - if (p->lower_path.dentry) - path_put(&p->lower_path); + path_put(&p->lower_path); call_rcu(&p->rcu, ecryptfs_dentry_free_rcu); } } -- cgit v1.2.3-59-g8ed1b From 87dc800be2499128efb3a6f059d75dc8e1e6d503 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 16 Sep 2013 10:30:04 -0400 Subject: new helper: kfree_put_link() duplicated to hell and back... Signed-off-by: Al Viro --- fs/cifs/cifsfs.c | 2 +- fs/cifs/cifsfs.h | 2 -- fs/cifs/link.c | 7 ------- fs/ecryptfs/inode.c | 12 +----------- fs/gfs2/inode.c | 9 +-------- fs/libfs.c | 10 ++++++++++ fs/proc/self.c | 10 +--------- include/linux/fs.h | 1 + 8 files changed, 15 insertions(+), 38 deletions(-) (limited to 'fs/ecryptfs') diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index a16b4e58bcc6..801975c34cf9 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -860,7 +860,7 @@ const struct inode_operations cifs_file_inode_ops = { const struct inode_operations cifs_symlink_inode_ops = { .readlink = generic_readlink, .follow_link = cifs_follow_link, - .put_link = cifs_put_link, + .put_link = kfree_put_link, .permission = cifs_permission, /* BB add the following two eventually */ /* revalidate: cifs_revalidate, diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h index 6d0b07217ac9..26a754f49ba1 100644 --- a/fs/cifs/cifsfs.h +++ b/fs/cifs/cifsfs.h @@ -115,8 +115,6 @@ extern struct vfsmount *cifs_dfs_d_automount(struct path *path); /* Functions related to symlinks */ extern void *cifs_follow_link(struct dentry *direntry, struct nameidata *nd); -extern void cifs_put_link(struct dentry *direntry, - struct nameidata *nd, void *); extern int cifs_readlink(struct dentry *direntry, char __user *buffer, int buflen); extern int cifs_symlink(struct inode *inode, struct dentry *direntry, diff --git a/fs/cifs/link.c b/fs/cifs/link.c index 7e36ceba0c7a..cc0234710ddb 100644 --- a/fs/cifs/link.c +++ b/fs/cifs/link.c @@ -621,10 +621,3 @@ symlink_exit: free_xid(xid); return rc; } - -void cifs_put_link(struct dentry *direntry, struct nameidata *nd, void *cookie) -{ - char *p = nd_get_link(nd); - if (!IS_ERR(p)) - kfree(p); -} diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 2ca62c40c6c2..0f9b66eaa767 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -703,16 +703,6 @@ out: return NULL; } -static void -ecryptfs_put_link(struct dentry *dentry, struct nameidata *nd, void *ptr) -{ - char *buf = nd_get_link(nd); - if (!IS_ERR(buf)) { - /* Free the char* */ - kfree(buf); - } -} - /** * upper_size_to_lower_size * @crypt_stat: Crypt_stat associated with file @@ -1121,7 +1111,7 @@ out: const struct inode_operations ecryptfs_symlink_iops = { .readlink = generic_readlink, .follow_link = ecryptfs_follow_link, - .put_link = ecryptfs_put_link, + .put_link = kfree_put_link, .permission = ecryptfs_permission, .setattr = ecryptfs_setattr, .getattr = ecryptfs_getattr_link, diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index ced3257f06e8..d2384f7c53e1 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1506,13 +1506,6 @@ out: return NULL; } -static void gfs2_put_link(struct dentry *dentry, struct nameidata *nd, void *p) -{ - char *s = nd_get_link(nd); - if (!IS_ERR(s)) - kfree(s); -} - /** * gfs2_permission - * @inode: The inode @@ -1864,7 +1857,7 @@ const struct inode_operations gfs2_dir_iops = { const struct inode_operations gfs2_symlink_iops = { .readlink = generic_readlink, .follow_link = gfs2_follow_link, - .put_link = gfs2_put_link, + .put_link = kfree_put_link, .permission = gfs2_permission, .setattr = gfs2_setattr, .getattr = gfs2_getattr, diff --git a/fs/libfs.c b/fs/libfs.c index 828622a31d30..8c5018493154 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include /* sync_mapping_buffers */ @@ -992,3 +993,12 @@ int noop_fsync(struct file *file, loff_t start, loff_t end, int datasync) return 0; } EXPORT_SYMBOL(noop_fsync); + +void kfree_put_link(struct dentry *dentry, struct nameidata *nd, + void *cookie) +{ + char *s = nd_get_link(nd); + if (!IS_ERR(s)) + kfree(s); +} +EXPORT_SYMBOL(kfree_put_link); diff --git a/fs/proc/self.c b/fs/proc/self.c index 6b6a993b5c25..ffeb202ec942 100644 --- a/fs/proc/self.c +++ b/fs/proc/self.c @@ -36,18 +36,10 @@ static void *proc_self_follow_link(struct dentry *dentry, struct nameidata *nd) return NULL; } -static void proc_self_put_link(struct dentry *dentry, struct nameidata *nd, - void *cookie) -{ - char *s = nd_get_link(nd); - if (!IS_ERR(s)) - kfree(s); -} - static const struct inode_operations proc_self_inode_operations = { .readlink = proc_self_readlink, .follow_link = proc_self_follow_link, - .put_link = proc_self_put_link, + .put_link = kfree_put_link, }; static unsigned self_inum; diff --git a/include/linux/fs.h b/include/linux/fs.h index 3f40547ba191..d80c2437d624 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2502,6 +2502,7 @@ extern int __page_symlink(struct inode *inode, const char *symname, int len, int nofs); extern int page_symlink(struct inode *inode, const char *symname, int len); extern const struct inode_operations page_symlink_inode_operations; +extern void kfree_put_link(struct dentry *, struct nameidata *, void *); extern int generic_readlink(struct dentry *, char __user *, int); extern void generic_fillattr(struct inode *, struct kstat *); extern int vfs_getattr(struct path *, struct kstat *); -- cgit v1.2.3-59-g8ed1b From 72c2d53192004845cbc19cd8a30b3212a9288140 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 22 Sep 2013 16:27:52 -0400 Subject: file->f_op is never NULL... Signed-off-by: Al Viro --- drivers/mtd/nand/nandsim.c | 2 +- drivers/staging/comedi/comedi_compat32.c | 3 --- fs/autofs4/autofs_i.h | 2 +- fs/autofs4/dev-ioctl.c | 6 ------ fs/binfmt_aout.c | 4 ++-- fs/binfmt_elf.c | 6 +++--- fs/binfmt_elf_fdpic.c | 2 +- fs/binfmt_em86.c | 2 +- fs/coda/dir.c | 3 --- fs/coda/file.c | 6 +++--- fs/compat_ioctl.c | 4 ++-- fs/coredump.c | 2 +- fs/ecryptfs/file.c | 8 ++++---- fs/eventpoll.c | 2 +- fs/exec.c | 35 ++++++++++++++------------------ fs/fcntl.c | 5 ++--- fs/file_table.c | 4 ++-- fs/ioctl.c | 4 ++-- fs/locks.c | 12 +++++------ fs/open.c | 8 ++++++-- fs/read_write.c | 25 ++++++++--------------- fs/readdir.c | 2 +- fs/select.c | 4 ++-- fs/splice.c | 6 +++--- fs/sync.c | 2 +- mm/memory.c | 2 +- mm/mmap.c | 4 ++-- mm/nommu.c | 2 +- net/9p/trans_fd.c | 4 ++-- 29 files changed, 74 insertions(+), 97 deletions(-) (limited to 'fs/ecryptfs') diff --git a/drivers/mtd/nand/nandsim.c b/drivers/mtd/nand/nandsim.c index bdc1d15369f8..d1c7de92cfdf 100644 --- a/drivers/mtd/nand/nandsim.c +++ b/drivers/mtd/nand/nandsim.c @@ -575,7 +575,7 @@ static int alloc_device(struct nandsim *ns) cfile = filp_open(cache_file, O_CREAT | O_RDWR | O_LARGEFILE, 0600); if (IS_ERR(cfile)) return PTR_ERR(cfile); - if (!cfile->f_op || (!cfile->f_op->read && !cfile->f_op->aio_read)) { + if (!cfile->f_op->read && !cfile->f_op->aio_read) { NS_ERR("alloc_device: cache file not readable\n"); err = -EINVAL; goto err_close; diff --git a/drivers/staging/comedi/comedi_compat32.c b/drivers/staging/comedi/comedi_compat32.c index 2dfb06aedb15..1e9da405d833 100644 --- a/drivers/staging/comedi/comedi_compat32.c +++ b/drivers/staging/comedi/comedi_compat32.c @@ -86,9 +86,6 @@ struct comedi32_insnlist_struct { static int translated_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { - if (!file->f_op) - return -ENOTTY; - if (file->f_op->unlocked_ioctl) return file->f_op->unlocked_ioctl(file, cmd, arg); diff --git a/fs/autofs4/autofs_i.h b/fs/autofs4/autofs_i.h index 3f1128b37e46..ebab147e9700 100644 --- a/fs/autofs4/autofs_i.h +++ b/fs/autofs4/autofs_i.h @@ -271,7 +271,7 @@ void autofs4_clean_ino(struct autofs_info *); static inline int autofs_prepare_pipe(struct file *pipe) { - if (!pipe->f_op || !pipe->f_op->write) + if (!pipe->f_op->write) return -EINVAL; if (!S_ISFIFO(file_inode(pipe)->i_mode)) return -EINVAL; diff --git a/fs/autofs4/dev-ioctl.c b/fs/autofs4/dev-ioctl.c index 0f00da329e71..1818ce7f5a06 100644 --- a/fs/autofs4/dev-ioctl.c +++ b/fs/autofs4/dev-ioctl.c @@ -658,12 +658,6 @@ static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __use goto out; } - if (!fp->f_op) { - err = -ENOTTY; - fput(fp); - goto out; - } - sbi = autofs_dev_ioctl_sbi(fp); if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { err = -EINVAL; diff --git a/fs/binfmt_aout.c b/fs/binfmt_aout.c index 89dec7f789a4..faaa819f70fa 100644 --- a/fs/binfmt_aout.c +++ b/fs/binfmt_aout.c @@ -221,7 +221,7 @@ static int load_aout_binary(struct linux_binprm * bprm) * Requires a mmap handler. This prevents people from using a.out * as part of an exploit attack against /proc-related vulnerabilities. */ - if (!bprm->file->f_op || !bprm->file->f_op->mmap) + if (!bprm->file->f_op->mmap) return -ENOEXEC; fd_offset = N_TXTOFF(ex); @@ -374,7 +374,7 @@ static int load_aout_library(struct file *file) * Requires a mmap handler. This prevents people from using a.out * as part of an exploit attack against /proc-related vulnerabilities. */ - if (!file->f_op || !file->f_op->mmap) + if (!file->f_op->mmap) goto out; if (N_FLAGS(ex)) diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 4c94a79991bb..501c8a4d6eb1 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -406,7 +406,7 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex, goto out; if (!elf_check_arch(interp_elf_ex)) goto out; - if (!interpreter->f_op || !interpreter->f_op->mmap) + if (!interpreter->f_op->mmap) goto out; /* @@ -607,7 +607,7 @@ static int load_elf_binary(struct linux_binprm *bprm) goto out; if (!elf_check_arch(&loc->elf_ex)) goto out; - if (!bprm->file->f_op || !bprm->file->f_op->mmap) + if (!bprm->file->f_op->mmap) goto out; /* Now read in all of the header information */ @@ -1028,7 +1028,7 @@ static int load_elf_library(struct file *file) /* First of all, some simple consistency checks */ if (elf_ex.e_type != ET_EXEC || elf_ex.e_phnum > 2 || - !elf_check_arch(&elf_ex) || !file->f_op || !file->f_op->mmap) + !elf_check_arch(&elf_ex) || !file->f_op->mmap) goto out; /* Now read in all of the header information */ diff --git a/fs/binfmt_elf_fdpic.c b/fs/binfmt_elf_fdpic.c index c166f325a183..ea4c6273b4a5 100644 --- a/fs/binfmt_elf_fdpic.c +++ b/fs/binfmt_elf_fdpic.c @@ -111,7 +111,7 @@ static int is_elf_fdpic(struct elfhdr *hdr, struct file *file) return 0; if (!elf_check_arch(hdr) || !elf_check_fdpic(hdr)) return 0; - if (!file->f_op || !file->f_op->mmap) + if (!file->f_op->mmap) return 0; return 1; } diff --git a/fs/binfmt_em86.c b/fs/binfmt_em86.c index 037a3e2b045b..f37b08cea1f7 100644 --- a/fs/binfmt_em86.c +++ b/fs/binfmt_em86.c @@ -38,7 +38,7 @@ static int load_em86(struct linux_binprm *bprm) /* First of all, some simple consistency checks */ if ((elf_ex.e_type != ET_EXEC && elf_ex.e_type != ET_DYN) || (!((elf_ex.e_machine == EM_386) || (elf_ex.e_machine == EM_486))) || - (!bprm->file->f_op || !bprm->file->f_op->mmap)) { + !bprm->file->f_op->mmap) { return -ENOEXEC; } diff --git a/fs/coda/dir.c b/fs/coda/dir.c index 190effc6a6fa..8c8d469c9283 100644 --- a/fs/coda/dir.c +++ b/fs/coda/dir.c @@ -387,9 +387,6 @@ static int coda_readdir(struct file *coda_file, struct dir_context *ctx) BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC); host_file = cfi->cfi_container; - if (!host_file->f_op) - return -ENOTDIR; - if (host_file->f_op->iterate) { struct inode *host_inode = file_inode(host_file); mutex_lock(&host_inode->i_mutex); diff --git a/fs/coda/file.c b/fs/coda/file.c index 380b798f8443..9e83b7790212 100644 --- a/fs/coda/file.c +++ b/fs/coda/file.c @@ -36,7 +36,7 @@ coda_file_read(struct file *coda_file, char __user *buf, size_t count, loff_t *p BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC); host_file = cfi->cfi_container; - if (!host_file->f_op || !host_file->f_op->read) + if (!host_file->f_op->read) return -EINVAL; return host_file->f_op->read(host_file, buf, count, ppos); @@ -75,7 +75,7 @@ coda_file_write(struct file *coda_file, const char __user *buf, size_t count, lo BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC); host_file = cfi->cfi_container; - if (!host_file->f_op || !host_file->f_op->write) + if (!host_file->f_op->write) return -EINVAL; host_inode = file_inode(host_file); @@ -105,7 +105,7 @@ coda_file_mmap(struct file *coda_file, struct vm_area_struct *vma) BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC); host_file = cfi->cfi_container; - if (!host_file->f_op || !host_file->f_op->mmap) + if (!host_file->f_op->mmap) return -ENODEV; coda_inode = file_inode(coda_file); diff --git a/fs/compat_ioctl.c b/fs/compat_ioctl.c index 5d19acfa7c6c..dc52e13d58e0 100644 --- a/fs/compat_ioctl.c +++ b/fs/compat_ioctl.c @@ -1583,13 +1583,13 @@ asmlinkage long compat_sys_ioctl(unsigned int fd, unsigned int cmd, /*FALL THROUGH*/ default: - if (f.file->f_op && f.file->f_op->compat_ioctl) { + if (f.file->f_op->compat_ioctl) { error = f.file->f_op->compat_ioctl(f.file, cmd, arg); if (error != -ENOIOCTLCMD) goto out_fput; } - if (!f.file->f_op || !f.file->f_op->unlocked_ioctl) + if (!f.file->f_op->unlocked_ioctl) goto do_ioctl; break; } diff --git a/fs/coredump.c b/fs/coredump.c index 9bdeca12ae0e..42c3b8423669 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -645,7 +645,7 @@ void do_coredump(siginfo_t *siginfo) */ if (!uid_eq(inode->i_uid, current_fsuid())) goto close_fail; - if (!cprm.file->f_op || !cprm.file->f_op->write) + if (!cprm.file->f_op->write) goto close_fail; if (do_truncate(cprm.file->f_path.dentry, 0, 0, cprm.file)) goto close_fail; diff --git a/fs/ecryptfs/file.c b/fs/ecryptfs/file.c index 992cf95830b5..6b1cd2b0c751 100644 --- a/fs/ecryptfs/file.c +++ b/fs/ecryptfs/file.c @@ -271,7 +271,7 @@ static int ecryptfs_flush(struct file *file, fl_owner_t td) { struct file *lower_file = ecryptfs_file_to_lower(file); - if (lower_file->f_op && lower_file->f_op->flush) { + if (lower_file->f_op->flush) { filemap_write_and_wait(file->f_mapping); return lower_file->f_op->flush(lower_file, td); } @@ -305,7 +305,7 @@ static int ecryptfs_fasync(int fd, struct file *file, int flag) struct file *lower_file = NULL; lower_file = ecryptfs_file_to_lower(file); - if (lower_file->f_op && lower_file->f_op->fasync) + if (lower_file->f_op->fasync) rc = lower_file->f_op->fasync(fd, lower_file, flag); return rc; } @@ -318,7 +318,7 @@ ecryptfs_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) if (ecryptfs_file_to_private(file)) lower_file = ecryptfs_file_to_lower(file); - if (lower_file && lower_file->f_op && lower_file->f_op->unlocked_ioctl) + if (lower_file->f_op && lower_file->f_op->unlocked_ioctl) rc = lower_file->f_op->unlocked_ioctl(lower_file, cmd, arg); return rc; } @@ -332,7 +332,7 @@ ecryptfs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) if (ecryptfs_file_to_private(file)) lower_file = ecryptfs_file_to_lower(file); - if (lower_file && lower_file->f_op && lower_file->f_op->compat_ioctl) + if (lower_file->f_op && lower_file->f_op->compat_ioctl) rc = lower_file->f_op->compat_ioctl(lower_file, cmd, arg); return rc; } diff --git a/fs/eventpoll.c b/fs/eventpoll.c index 473e09da7d02..31fd77cd81eb 100644 --- a/fs/eventpoll.c +++ b/fs/eventpoll.c @@ -1816,7 +1816,7 @@ SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd, /* The target file descriptor must support poll */ error = -EPERM; - if (!tf.file->f_op || !tf.file->f_op->poll) + if (!tf.file->f_op->poll) goto error_tgt_fput; /* Check if EPOLLWAKEUP is allowed */ diff --git a/fs/exec.c b/fs/exec.c index 8875dd10ae7a..351440fbf1ee 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -106,6 +106,7 @@ static inline void put_binfmt(struct linux_binfmt * fmt) */ SYSCALL_DEFINE1(uselib, const char __user *, library) { + struct linux_binfmt *fmt; struct file *file; struct filename *tmp = getname(library); int error = PTR_ERR(tmp); @@ -136,24 +137,21 @@ SYSCALL_DEFINE1(uselib, const char __user *, library) fsnotify_open(file); error = -ENOEXEC; - if(file->f_op) { - struct linux_binfmt * fmt; - read_lock(&binfmt_lock); - list_for_each_entry(fmt, &formats, lh) { - if (!fmt->load_shlib) - continue; - if (!try_module_get(fmt->module)) - continue; - read_unlock(&binfmt_lock); - error = fmt->load_shlib(file); - read_lock(&binfmt_lock); - put_binfmt(fmt); - if (error != -ENOEXEC) - break; - } + read_lock(&binfmt_lock); + list_for_each_entry(fmt, &formats, lh) { + if (!fmt->load_shlib) + continue; + if (!try_module_get(fmt->module)) + continue; read_unlock(&binfmt_lock); + error = fmt->load_shlib(file); + read_lock(&binfmt_lock); + put_binfmt(fmt); + if (error != -ENOEXEC) + break; } + read_unlock(&binfmt_lock); exit: fput(file); out: @@ -1277,13 +1275,10 @@ static int check_unsafe_exec(struct linux_binprm *bprm) */ int prepare_binprm(struct linux_binprm *bprm) { - umode_t mode; - struct inode * inode = file_inode(bprm->file); + struct inode *inode = file_inode(bprm->file); + umode_t mode = inode->i_mode; int retval; - mode = inode->i_mode; - if (bprm->file->f_op == NULL) - return -EACCES; /* clear any previous set[ug]id data from a previous binary */ bprm->cred->euid = current_euid(); diff --git a/fs/fcntl.c b/fs/fcntl.c index 65343c3741ff..ef6866592a0f 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -56,7 +56,7 @@ static int setfl(int fd, struct file * filp, unsigned long arg) return -EINVAL; } - if (filp->f_op && filp->f_op->check_flags) + if (filp->f_op->check_flags) error = filp->f_op->check_flags(arg); if (error) return error; @@ -64,8 +64,7 @@ static int setfl(int fd, struct file * filp, unsigned long arg) /* * ->fasync() is responsible for setting the FASYNC bit. */ - if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op && - filp->f_op->fasync) { + if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) { error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0); if (error < 0) goto out; diff --git a/fs/file_table.c b/fs/file_table.c index abdd15ad13c9..e61e5529fa9d 100644 --- a/fs/file_table.c +++ b/fs/file_table.c @@ -240,11 +240,11 @@ static void __fput(struct file *file) locks_remove_flock(file); if (unlikely(file->f_flags & FASYNC)) { - if (file->f_op && file->f_op->fasync) + if (file->f_op->fasync) file->f_op->fasync(-1, file, 0); } ima_file_free(file); - if (file->f_op && file->f_op->release) + if (file->f_op->release) file->f_op->release(inode, file); security_file_free(file); if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != NULL && diff --git a/fs/ioctl.c b/fs/ioctl.c index fd507fb460f8..8ac3fad36192 100644 --- a/fs/ioctl.c +++ b/fs/ioctl.c @@ -37,7 +37,7 @@ static long vfs_ioctl(struct file *filp, unsigned int cmd, { int error = -ENOTTY; - if (!filp->f_op || !filp->f_op->unlocked_ioctl) + if (!filp->f_op->unlocked_ioctl) goto out; error = filp->f_op->unlocked_ioctl(filp, cmd, arg); @@ -501,7 +501,7 @@ static int ioctl_fioasync(unsigned int fd, struct file *filp, /* Did FASYNC state change ? */ if ((flag ^ filp->f_flags) & FASYNC) { - if (filp->f_op && filp->f_op->fasync) + if (filp->f_op->fasync) /* fasync() adjusts filp->f_flags */ error = filp->f_op->fasync(fd, filp, on); else diff --git a/fs/locks.c b/fs/locks.c index b27a3005d78d..f155ffd648bb 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -1579,7 +1579,7 @@ EXPORT_SYMBOL(generic_setlease); static int __vfs_setlease(struct file *filp, long arg, struct file_lock **lease) { - if (filp->f_op && filp->f_op->setlease) + if (filp->f_op->setlease) return filp->f_op->setlease(filp, arg, lease); else return generic_setlease(filp, arg, lease); @@ -1771,7 +1771,7 @@ SYSCALL_DEFINE2(flock, unsigned int, fd, unsigned int, cmd) if (error) goto out_free; - if (f.file->f_op && f.file->f_op->flock) + if (f.file->f_op->flock) error = f.file->f_op->flock(f.file, (can_sleep) ? F_SETLKW : F_SETLK, lock); @@ -1797,7 +1797,7 @@ SYSCALL_DEFINE2(flock, unsigned int, fd, unsigned int, cmd) */ int vfs_test_lock(struct file *filp, struct file_lock *fl) { - if (filp->f_op && filp->f_op->lock) + if (filp->f_op->lock) return filp->f_op->lock(filp, F_GETLK, fl); posix_test_lock(filp, fl); return 0; @@ -1909,7 +1909,7 @@ out: */ int vfs_lock_file(struct file *filp, unsigned int cmd, struct file_lock *fl, struct file_lock *conf) { - if (filp->f_op && filp->f_op->lock) + if (filp->f_op->lock) return filp->f_op->lock(filp, cmd, fl); else return posix_lock_file(filp, fl, conf); @@ -2182,7 +2182,7 @@ void locks_remove_flock(struct file *filp) if (!inode->i_flock) return; - if (filp->f_op && filp->f_op->flock) { + if (filp->f_op->flock) { struct file_lock fl = { .fl_pid = current->tgid, .fl_file = filp, @@ -2246,7 +2246,7 @@ EXPORT_SYMBOL(posix_unblock_lock); */ int vfs_cancel_lock(struct file *filp, struct file_lock *fl) { - if (filp->f_op && filp->f_op->lock) + if (filp->f_op->lock) return filp->f_op->lock(filp, F_CANCELLK, fl); return 0; } diff --git a/fs/open.c b/fs/open.c index d420331ca32a..a1465b1ec8c7 100644 --- a/fs/open.c +++ b/fs/open.c @@ -693,6 +693,10 @@ static int do_dentry_open(struct file *f, } f->f_op = fops_get(inode->i_fop); + if (unlikely(WARN_ON(!f->f_op))) { + error = -ENODEV; + goto cleanup_all; + } error = security_file_open(f, cred); if (error) @@ -702,7 +706,7 @@ static int do_dentry_open(struct file *f, if (error) goto cleanup_all; - if (!open && f->f_op) + if (!open) open = f->f_op->open; if (open) { error = open(inode, f); @@ -1023,7 +1027,7 @@ int filp_close(struct file *filp, fl_owner_t id) return 0; } - if (filp->f_op && filp->f_op->flush) + if (filp->f_op->flush) retval = filp->f_op->flush(filp, id); if (likely(!(filp->f_mode & FMODE_PATH))) { diff --git a/fs/read_write.c b/fs/read_write.c index e3cd280b158c..58e440df1bc6 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -257,7 +257,7 @@ loff_t vfs_llseek(struct file *file, loff_t offset, int whence) fn = no_llseek; if (file->f_mode & FMODE_LSEEK) { - if (file->f_op && file->f_op->llseek) + if (file->f_op->llseek) fn = file->f_op->llseek; } return fn(file, offset, whence); @@ -384,7 +384,7 @@ ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos) if (!(file->f_mode & FMODE_READ)) return -EBADF; - if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read)) + if (!file->f_op->read && !file->f_op->aio_read) return -EINVAL; if (unlikely(!access_ok(VERIFY_WRITE, buf, count))) return -EFAULT; @@ -433,7 +433,7 @@ ssize_t __kernel_write(struct file *file, const char *buf, size_t count, loff_t const char __user *p; ssize_t ret; - if (!file->f_op || (!file->f_op->write && !file->f_op->aio_write)) + if (!file->f_op->write && !file->f_op->aio_write) return -EINVAL; old_fs = get_fs(); @@ -460,7 +460,7 @@ ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_ if (!(file->f_mode & FMODE_WRITE)) return -EBADF; - if (!file->f_op || (!file->f_op->write && !file->f_op->aio_write)) + if (!file->f_op->write && !file->f_op->aio_write) return -EINVAL; if (unlikely(!access_ok(VERIFY_READ, buf, count))) return -EFAULT; @@ -727,11 +727,6 @@ static ssize_t do_readv_writev(int type, struct file *file, io_fn_t fn; iov_fn_t fnv; - if (!file->f_op) { - ret = -EINVAL; - goto out; - } - ret = rw_copy_check_uvector(type, uvector, nr_segs, ARRAY_SIZE(iovstack), iovstack, &iov); if (ret <= 0) @@ -778,7 +773,7 @@ ssize_t vfs_readv(struct file *file, const struct iovec __user *vec, { if (!(file->f_mode & FMODE_READ)) return -EBADF; - if (!file->f_op || (!file->f_op->aio_read && !file->f_op->read)) + if (!file->f_op->aio_read && !file->f_op->read) return -EINVAL; return do_readv_writev(READ, file, vec, vlen, pos); @@ -791,7 +786,7 @@ ssize_t vfs_writev(struct file *file, const struct iovec __user *vec, { if (!(file->f_mode & FMODE_WRITE)) return -EBADF; - if (!file->f_op || (!file->f_op->aio_write && !file->f_op->write)) + if (!file->f_op->aio_write && !file->f_op->write) return -EINVAL; return do_readv_writev(WRITE, file, vec, vlen, pos); @@ -906,10 +901,6 @@ static ssize_t compat_do_readv_writev(int type, struct file *file, io_fn_t fn; iov_fn_t fnv; - ret = -EINVAL; - if (!file->f_op) - goto out; - ret = -EFAULT; if (!access_ok(VERIFY_READ, uvector, nr_segs*sizeof(*uvector))) goto out; @@ -965,7 +956,7 @@ static size_t compat_readv(struct file *file, goto out; ret = -EINVAL; - if (!file->f_op || (!file->f_op->aio_read && !file->f_op->read)) + if (!file->f_op->aio_read && !file->f_op->read) goto out; ret = compat_do_readv_writev(READ, file, vec, vlen, pos); @@ -1032,7 +1023,7 @@ static size_t compat_writev(struct file *file, goto out; ret = -EINVAL; - if (!file->f_op || (!file->f_op->aio_write && !file->f_op->write)) + if (!file->f_op->aio_write && !file->f_op->write) goto out; ret = compat_do_readv_writev(WRITE, file, vec, vlen, pos); diff --git a/fs/readdir.c b/fs/readdir.c index 93d71e574310..5b53d995cae6 100644 --- a/fs/readdir.c +++ b/fs/readdir.c @@ -24,7 +24,7 @@ int iterate_dir(struct file *file, struct dir_context *ctx) { struct inode *inode = file_inode(file); int res = -ENOTDIR; - if (!file->f_op || !file->f_op->iterate) + if (!file->f_op->iterate) goto out; res = security_file_permission(file, MAY_READ); diff --git a/fs/select.c b/fs/select.c index 35d4adc749d9..55278a36a658 100644 --- a/fs/select.c +++ b/fs/select.c @@ -455,7 +455,7 @@ int do_select(int n, fd_set_bits *fds, struct timespec *end_time) const struct file_operations *f_op; f_op = f.file->f_op; mask = DEFAULT_POLLMASK; - if (f_op && f_op->poll) { + if (f_op->poll) { wait_key_set(wait, in, out, bit, busy_flag); mask = (*f_op->poll)(f.file, wait); @@ -762,7 +762,7 @@ static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait, mask = POLLNVAL; if (f.file) { mask = DEFAULT_POLLMASK; - if (f.file->f_op && f.file->f_op->poll) { + if (f.file->f_op->poll) { pwait->_key = pollfd->events|POLLERR|POLLHUP; pwait->_key |= busy_flag; mask = f.file->f_op->poll(f.file, pwait); diff --git a/fs/splice.c b/fs/splice.c index 3b7ee656f3aa..46a08f772d7d 100644 --- a/fs/splice.c +++ b/fs/splice.c @@ -695,7 +695,7 @@ static int pipe_to_sendpage(struct pipe_inode_info *pipe, loff_t pos = sd->pos; int more; - if (!likely(file->f_op && file->f_op->sendpage)) + if (!likely(file->f_op->sendpage)) return -EINVAL; more = (sd->flags & SPLICE_F_MORE) ? MSG_MORE : 0; @@ -1099,7 +1099,7 @@ static long do_splice_from(struct pipe_inode_info *pipe, struct file *out, ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); - if (out->f_op && out->f_op->splice_write) + if (out->f_op->splice_write) splice_write = out->f_op->splice_write; else splice_write = default_file_splice_write; @@ -1125,7 +1125,7 @@ static long do_splice_to(struct file *in, loff_t *ppos, if (unlikely(ret < 0)) return ret; - if (in->f_op && in->f_op->splice_read) + if (in->f_op->splice_read) splice_read = in->f_op->splice_read; else splice_read = default_file_splice_read; diff --git a/fs/sync.c b/fs/sync.c index 905f3f6b3d85..6c0ca3b75758 100644 --- a/fs/sync.c +++ b/fs/sync.c @@ -177,7 +177,7 @@ SYSCALL_DEFINE1(syncfs, int, fd) */ int vfs_fsync_range(struct file *file, loff_t start, loff_t end, int datasync) { - if (!file->f_op || !file->f_op->fsync) + if (!file->f_op->fsync) return -EINVAL; return file->f_op->fsync(file, start, end, datasync); } diff --git a/mm/memory.c b/mm/memory.c index ca0003947115..e2bbba42604c 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -681,7 +681,7 @@ static void print_bad_pte(struct vm_area_struct *vma, unsigned long addr, if (vma->vm_ops) printk(KERN_ALERT "vma->vm_ops->fault: %pSR\n", vma->vm_ops->fault); - if (vma->vm_file && vma->vm_file->f_op) + if (vma->vm_file) printk(KERN_ALERT "vma->vm_file->f_op->mmap: %pSR\n", vma->vm_file->f_op->mmap); dump_stack(); diff --git a/mm/mmap.c b/mm/mmap.c index 9d548512ff8a..ab199dfc9e26 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -1299,7 +1299,7 @@ unsigned long do_mmap_pgoff(struct file *file, unsigned long addr, vm_flags &= ~VM_MAYEXEC; } - if (!file->f_op || !file->f_op->mmap) + if (!file->f_op->mmap) return -ENODEV; if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP)) return -EINVAL; @@ -1951,7 +1951,7 @@ get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, return -ENOMEM; get_area = current->mm->get_unmapped_area; - if (file && file->f_op && file->f_op->get_unmapped_area) + if (file && file->f_op->get_unmapped_area) get_area = file->f_op->get_unmapped_area; addr = get_area(file, addr, len, pgoff, flags); if (IS_ERR_VALUE(addr)) diff --git a/mm/nommu.c b/mm/nommu.c index ecd1f158548e..9e6cb02cba64 100644 --- a/mm/nommu.c +++ b/mm/nommu.c @@ -937,7 +937,7 @@ static int validate_mmap_request(struct file *file, struct address_space *mapping; /* files must support mmap */ - if (!file->f_op || !file->f_op->mmap) + if (!file->f_op->mmap) return -ENODEV; /* work out if what we've got could possibly be shared diff --git a/net/9p/trans_fd.c b/net/9p/trans_fd.c index 3ffda1b3799b..9321a7763067 100644 --- a/net/9p/trans_fd.c +++ b/net/9p/trans_fd.c @@ -244,10 +244,10 @@ p9_fd_poll(struct p9_client *client, struct poll_table_struct *pt) if (!ts) return -EREMOTEIO; - if (!ts->rd->f_op || !ts->rd->f_op->poll) + if (!ts->rd->f_op->poll) return -EIO; - if (!ts->wr->f_op || !ts->wr->f_op->poll) + if (!ts->wr->f_op->poll) return -EIO; ret = ts->rd->f_op->poll(ts->rd, pt); -- cgit v1.2.3-59-g8ed1b From b21996e36c8e3b92a84e972378bde80b43acd890 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Tue, 20 Sep 2011 09:14:34 -0400 Subject: locks: break delegations on unlink We need to break delegations on any operation that changes the set of links pointing to an inode. Start with unlink. Such operations also hold the i_mutex on a parent directory. Breaking a delegation may require waiting for a timeout (by default 90 seconds) in the case of a unresponsive NFS client. To avoid blocking all directory operations, we therefore drop locks before waiting for the delegation. The logic then looks like: acquire locks ... test for delegation; if found: take reference on inode release locks wait for delegation break drop reference on inode retry It is possible this could never terminate. (Even if we take precautions to prevent another delegation being acquired on the same inode, we could get a different inode on each retry.) But this seems very unlikely. The initial test for a delegation happens after the lock on the target inode is acquired, but the directory inode may have been acquired further up the call stack. We therefore add a "struct inode **" argument to any intervening functions, which we use to pass the inode back up to the caller in the case it needs a delegation synchronously broken. Cc: David Howells Cc: Tyler Hicks Cc: Dustin Kirkland Acked-by: Jeff Layton Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- drivers/base/devtmpfs.c | 2 +- fs/cachefiles/namei.c | 2 +- fs/ecryptfs/inode.c | 4 ++-- fs/namei.c | 42 +++++++++++++++++++++++++++++++++++++++--- fs/nfsd/vfs.c | 2 +- include/linux/fs.h | 2 +- ipc/mqueue.c | 2 +- 7 files changed, 46 insertions(+), 10 deletions(-) (limited to 'fs/ecryptfs') diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index 7413d065906b..1b8490e2fbde 100644 --- a/drivers/base/devtmpfs.c +++ b/drivers/base/devtmpfs.c @@ -324,7 +324,7 @@ static int handle_remove(const char *nodename, struct device *dev) mutex_lock(&dentry->d_inode->i_mutex); notify_change(dentry, &newattrs); mutex_unlock(&dentry->d_inode->i_mutex); - err = vfs_unlink(parent.dentry->d_inode, dentry); + err = vfs_unlink(parent.dentry->d_inode, dentry, NULL); if (!err || err == -ENOENT) deleted = 1; } diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c index f4a08d7fa2f7..31d480c0e046 100644 --- a/fs/cachefiles/namei.c +++ b/fs/cachefiles/namei.c @@ -294,7 +294,7 @@ static int cachefiles_bury_object(struct cachefiles_cache *cache, if (ret < 0) { cachefiles_io_error(cache, "Unlink security error"); } else { - ret = vfs_unlink(dir->d_inode, rep); + ret = vfs_unlink(dir->d_inode, rep, NULL); if (preemptive) cachefiles_mark_object_buried(cache, rep); diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 0f9b66eaa767..dc60b8bd09ec 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -153,7 +153,7 @@ static int ecryptfs_do_unlink(struct inode *dir, struct dentry *dentry, dget(lower_dentry); lower_dir_dentry = lock_parent(lower_dentry); - rc = vfs_unlink(lower_dir_inode, lower_dentry); + rc = vfs_unlink(lower_dir_inode, lower_dentry, NULL); if (rc) { printk(KERN_ERR "Error in vfs_unlink; rc = [%d]\n", rc); goto out_unlock; @@ -208,7 +208,7 @@ ecryptfs_do_create(struct inode *directory_inode, inode = __ecryptfs_get_inode(lower_dentry->d_inode, directory_inode->i_sb); if (IS_ERR(inode)) { - vfs_unlink(lower_dir_dentry->d_inode, lower_dentry); + vfs_unlink(lower_dir_dentry->d_inode, lower_dentry, NULL); goto out_lock; } fsstack_copy_attr_times(directory_inode, lower_dir_dentry->d_inode); diff --git a/fs/namei.c b/fs/namei.c index e633a58d4222..67ce331a3ed8 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3615,7 +3615,25 @@ SYSCALL_DEFINE1(rmdir, const char __user *, pathname) return do_rmdir(AT_FDCWD, pathname); } -int vfs_unlink(struct inode *dir, struct dentry *dentry) +/** + * vfs_unlink - unlink a filesystem object + * @dir: parent directory + * @dentry: victim + * @delegated_inode: returns victim inode, if the inode is delegated. + * + * The caller must hold dir->i_mutex. + * + * If vfs_unlink discovers a delegation, it will return -EWOULDBLOCK and + * return a reference to the inode in delegated_inode. The caller + * should then break the delegation on that inode and retry. Because + * breaking a delegation may take a long time, the caller should drop + * dir->i_mutex before doing so. + * + * Alternatively, a caller may pass NULL for delegated_inode. This may + * be appropriate for callers that expect the underlying filesystem not + * to be NFS exported. + */ +int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegated_inode) { struct inode *target = dentry->d_inode; int error = may_delete(dir, dentry, 0); @@ -3632,11 +3650,20 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry) else { error = security_inode_unlink(dir, dentry); if (!error) { + error = break_deleg(target, O_WRONLY|O_NONBLOCK); + if (error) { + if (error == -EWOULDBLOCK && delegated_inode) { + *delegated_inode = target; + ihold(target); + } + goto out; + } error = dir->i_op->unlink(dir, dentry); if (!error) dont_mount(dentry); } } +out: mutex_unlock(&target->i_mutex); /* We don't d_delete() NFS sillyrenamed files--they still exist. */ @@ -3661,6 +3688,7 @@ static long do_unlinkat(int dfd, const char __user *pathname) struct dentry *dentry; struct nameidata nd; struct inode *inode = NULL; + struct inode *delegated_inode = NULL; unsigned int lookup_flags = 0; retry: name = user_path_parent(dfd, pathname, &nd, lookup_flags); @@ -3675,7 +3703,7 @@ retry: error = mnt_want_write(nd.path.mnt); if (error) goto exit1; - +retry_deleg: mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT); dentry = lookup_hash(&nd); error = PTR_ERR(dentry); @@ -3690,13 +3718,21 @@ retry: error = security_path_unlink(&nd.path, dentry); if (error) goto exit2; - error = vfs_unlink(nd.path.dentry->d_inode, dentry); + error = vfs_unlink(nd.path.dentry->d_inode, dentry, &delegated_inode); exit2: dput(dentry); } mutex_unlock(&nd.path.dentry->d_inode->i_mutex); if (inode) iput(inode); /* truncate the inode here */ + inode = NULL; + if (delegated_inode) { + error = break_deleg(delegated_inode, O_WRONLY); + iput(delegated_inode); + delegated_inode = NULL; + if (!error) + goto retry_deleg; + } mnt_drop_write(nd.path.mnt); exit1: path_put(&nd.path); diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 13886f7f40d5..7a810235d599 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -1910,7 +1910,7 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, if (host_err) goto out_put; if (type != S_IFDIR) - host_err = vfs_unlink(dirp, rdentry); + host_err = vfs_unlink(dirp, rdentry, NULL); else host_err = vfs_rmdir(dirp, rdentry); if (!host_err) diff --git a/include/linux/fs.h b/include/linux/fs.h index 8e4be1be1a62..a5799233142a 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1455,7 +1455,7 @@ extern int vfs_mknod(struct inode *, struct dentry *, umode_t, dev_t); extern int vfs_symlink(struct inode *, struct dentry *, const char *); extern int vfs_link(struct dentry *, struct inode *, struct dentry *); extern int vfs_rmdir(struct inode *, struct dentry *); -extern int vfs_unlink(struct inode *, struct dentry *); +extern int vfs_unlink(struct inode *, struct dentry *, struct inode **); extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *); /* diff --git a/ipc/mqueue.c b/ipc/mqueue.c index ae1996d3c539..95827ce2f3c7 100644 --- a/ipc/mqueue.c +++ b/ipc/mqueue.c @@ -886,7 +886,7 @@ SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name) err = -ENOENT; } else { ihold(inode); - err = vfs_unlink(dentry->d_parent->d_inode, dentry); + err = vfs_unlink(dentry->d_parent->d_inode, dentry, NULL); } dput(dentry); -- cgit v1.2.3-59-g8ed1b From 8e6d782cab50884ba94324632700e6233a252f6a Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Tue, 20 Sep 2011 16:59:58 -0400 Subject: locks: break delegations on rename Cc: David Howells Acked-by: Jeff Layton Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- .../lustre/lustre/include/linux/lustre_compat25.h | 4 +- drivers/staging/lustre/lustre/lvfs/lvfs_linux.c | 2 +- fs/cachefiles/namei.c | 2 +- fs/ecryptfs/inode.c | 3 +- fs/namei.c | 47 ++++++++++++++++++++-- fs/nfsd/vfs.c | 2 +- include/linux/fs.h | 2 +- 7 files changed, 51 insertions(+), 11 deletions(-) (limited to 'fs/ecryptfs') diff --git a/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h b/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h index 9243dfab43d3..80b019bf969c 100644 --- a/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h +++ b/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h @@ -105,8 +105,8 @@ static inline void ll_set_fs_pwd(struct fs_struct *fs, struct vfsmount *mnt, #define ll_vfs_unlink(inode,entry,mnt) vfs_unlink(inode,entry) #define ll_vfs_mknod(dir,entry,mnt,mode,dev) vfs_mknod(dir,entry,mode,dev) #define ll_security_inode_unlink(dir,entry,mnt) security_inode_unlink(dir,entry) -#define ll_vfs_rename(old,old_dir,mnt,new,new_dir,mnt1) \ - vfs_rename(old,old_dir,new,new_dir) +#define ll_vfs_rename(old,old_dir,mnt,new,new_dir,mnt1,delegated_inode) \ + vfs_rename(old,old_dir,new,new_dir,delegated_inode) #define cfs_bio_io_error(a,b) bio_io_error((a)) #define cfs_bio_endio(a,b,c) bio_endio((a),(c)) diff --git a/drivers/staging/lustre/lustre/lvfs/lvfs_linux.c b/drivers/staging/lustre/lustre/lvfs/lvfs_linux.c index 18e1b47a1d65..4ed7c9f0a8be 100644 --- a/drivers/staging/lustre/lustre/lvfs/lvfs_linux.c +++ b/drivers/staging/lustre/lustre/lvfs/lvfs_linux.c @@ -220,7 +220,7 @@ int lustre_rename(struct dentry *dir, struct vfsmount *mnt, GOTO(put_old, err = PTR_ERR(dchild_new)); err = ll_vfs_rename(dir->d_inode, dchild_old, mnt, - dir->d_inode, dchild_new, mnt); + dir->d_inode, dchild_new, mnt, NULL); dput(dchild_new); put_old: diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c index 31d480c0e046..ca65f39dc8dc 100644 --- a/fs/cachefiles/namei.c +++ b/fs/cachefiles/namei.c @@ -396,7 +396,7 @@ try_again: cachefiles_io_error(cache, "Rename security error %d", ret); } else { ret = vfs_rename(dir->d_inode, rep, - cache->graveyard->d_inode, grave); + cache->graveyard->d_inode, grave, NULL); if (ret != 0 && ret != -ENOMEM) cachefiles_io_error(cache, "Rename failed with error %d", ret); diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index dc60b8bd09ec..c23b01bb7e04 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -640,7 +640,8 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry, goto out_lock; } rc = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry, - lower_new_dir_dentry->d_inode, lower_new_dentry); + lower_new_dir_dentry->d_inode, lower_new_dentry, + NULL); if (rc) goto out_lock; if (target_inode) diff --git a/fs/namei.c b/fs/namei.c index cfaeaae0f2db..ce7e580e4e14 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4022,7 +4022,8 @@ out: } static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + struct inode **delegated_inode) { struct inode *target = new_dentry->d_inode; struct inode *source = old_dentry->d_inode; @@ -4039,6 +4040,14 @@ static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry, if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry)) goto out; + error = try_break_deleg(source, delegated_inode); + if (error) + goto out; + if (target) { + error = try_break_deleg(target, delegated_inode); + if (error) + goto out; + } error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry); if (error) goto out; @@ -4053,8 +4062,30 @@ out: return error; } +/** + * vfs_rename - rename a filesystem object + * @old_dir: parent of source + * @old_dentry: source + * @new_dir: parent of destination + * @new_dentry: destination + * @delegated_inode: returns an inode needing a delegation break + * + * The caller must hold multiple mutexes--see lock_rename()). + * + * If vfs_rename discovers a delegation in need of breaking at either + * the source or destination, it will return -EWOULDBLOCK and return a + * reference to the inode in delegated_inode. The caller should then + * break the delegation and retry. Because breaking a delegation may + * take a long time, the caller should drop all locks before doing + * so. + * + * Alternatively, a caller may pass NULL for delegated_inode. This may + * be appropriate for callers that expect the underlying filesystem not + * to be NFS exported. + */ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) + struct inode *new_dir, struct dentry *new_dentry, + struct inode **delegated_inode) { int error; int is_dir = d_is_directory(old_dentry) || d_is_autodir(old_dentry); @@ -4082,7 +4113,7 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, if (is_dir) error = vfs_rename_dir(old_dir,old_dentry,new_dir,new_dentry); else - error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry); + error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry,delegated_inode); if (!error) fsnotify_move(old_dir, new_dir, old_name, is_dir, new_dentry->d_inode, old_dentry); @@ -4098,6 +4129,7 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname, struct dentry *old_dentry, *new_dentry; struct dentry *trap; struct nameidata oldnd, newnd; + struct inode *delegated_inode = NULL; struct filename *from; struct filename *to; unsigned int lookup_flags = 0; @@ -4137,6 +4169,7 @@ retry: newnd.flags &= ~LOOKUP_PARENT; newnd.flags |= LOOKUP_RENAME_TARGET; +retry_deleg: trap = lock_rename(new_dir, old_dir); old_dentry = lookup_hash(&oldnd); @@ -4173,13 +4206,19 @@ retry: if (error) goto exit5; error = vfs_rename(old_dir->d_inode, old_dentry, - new_dir->d_inode, new_dentry); + new_dir->d_inode, new_dentry, + &delegated_inode); exit5: dput(new_dentry); exit4: dput(old_dentry); exit3: unlock_rename(new_dir, old_dir); + if (delegated_inode) { + error = break_deleg_wait(&delegated_inode); + if (!error) + goto retry_deleg; + } mnt_drop_write(oldnd.path.mnt); exit2: if (retry_estale(error, lookup_flags)) diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 7a810235d599..45bf0295894d 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -1837,7 +1837,7 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen, if (host_err) goto out_dput_new; } - host_err = vfs_rename(fdir, odentry, tdir, ndentry); + host_err = vfs_rename(fdir, odentry, tdir, ndentry, NULL); if (!host_err) { host_err = commit_metadata(tfhp); if (!host_err) diff --git a/include/linux/fs.h b/include/linux/fs.h index 931f919f44e1..5bcff883fa90 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1456,7 +1456,7 @@ extern int vfs_symlink(struct inode *, struct dentry *, const char *); extern int vfs_link(struct dentry *, struct inode *, struct dentry *); extern int vfs_rmdir(struct inode *, struct dentry *); extern int vfs_unlink(struct inode *, struct dentry *, struct inode **); -extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *); +extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, struct inode **); /* * VFS dentry helper functions. -- cgit v1.2.3-59-g8ed1b From 146a8595c6399ee6ab4b5cc34c0d28aa4835fdc5 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Tue, 20 Sep 2011 17:14:31 -0400 Subject: locks: break delegations on link Cc: Tyler Hicks Cc: Dustin Kirkland Acked-by: Jeff Layton Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- fs/ecryptfs/inode.c | 2 +- fs/namei.c | 36 ++++++++++++++++++++++++++++++++---- fs/nfsd/vfs.c | 2 +- include/linux/fs.h | 2 +- 4 files changed, 35 insertions(+), 7 deletions(-) (limited to 'fs/ecryptfs') diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index c23b01bb7e04..1c628f023041 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -475,7 +475,7 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir, dget(lower_new_dentry); lower_dir_dentry = lock_parent(lower_new_dentry); rc = vfs_link(lower_old_dentry, lower_dir_dentry->d_inode, - lower_new_dentry); + lower_new_dentry, NULL); if (rc || !lower_new_dentry->d_inode) goto out_lock; rc = ecryptfs_interpose(lower_new_dentry, new_dentry, dir->i_sb); diff --git a/fs/namei.c b/fs/namei.c index ce7e580e4e14..251178a1e383 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3819,7 +3819,26 @@ SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn return sys_symlinkat(oldname, AT_FDCWD, newname); } -int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) +/** + * vfs_link - create a new link + * @old_dentry: object to be linked + * @dir: new parent + * @new_dentry: where to create the new link + * @delegated_inode: returns inode needing a delegation break + * + * The caller must hold dir->i_mutex + * + * If vfs_link discovers a delegation on the to-be-linked file in need + * of breaking, it will return -EWOULDBLOCK and return a reference to the + * inode in delegated_inode. The caller should then break the delegation + * and retry. Because breaking a delegation may take a long time, the + * caller should drop the i_mutex before doing so. + * + * Alternatively, a caller may pass NULL for delegated_inode. This may + * be appropriate for callers that expect the underlying filesystem not + * to be NFS exported. + */ +int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry, struct inode **delegated_inode) { struct inode *inode = old_dentry->d_inode; unsigned max_links = dir->i_sb->s_max_links; @@ -3855,8 +3874,11 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de error = -ENOENT; else if (max_links && inode->i_nlink >= max_links) error = -EMLINK; - else - error = dir->i_op->link(old_dentry, dir, new_dentry); + else { + error = try_break_deleg(inode, delegated_inode); + if (!error) + error = dir->i_op->link(old_dentry, dir, new_dentry); + } if (!error && (inode->i_state & I_LINKABLE)) { spin_lock(&inode->i_lock); @@ -3883,6 +3905,7 @@ SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname, { struct dentry *new_dentry; struct path old_path, new_path; + struct inode *delegated_inode = NULL; int how = 0; int error; @@ -3921,9 +3944,14 @@ retry: error = security_path_link(old_path.dentry, &new_path, new_dentry); if (error) goto out_dput; - error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry); + error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry, &delegated_inode); out_dput: done_path_create(&new_path, new_dentry); + if (delegated_inode) { + error = break_deleg_wait(&delegated_inode); + if (!error) + goto retry; + } if (retry_estale(error, how)) { how |= LOOKUP_REVAL; goto retry; diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 45bf0295894d..27ba21b5f383 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -1736,7 +1736,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp, err = nfserrno(host_err); goto out_dput; } - host_err = vfs_link(dold, dirp, dnew); + host_err = vfs_link(dold, dirp, dnew, NULL); if (!host_err) { err = nfserrno(commit_metadata(ffhp)); if (!err) diff --git a/include/linux/fs.h b/include/linux/fs.h index 5bcff883fa90..6e36e7118ec1 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1453,7 +1453,7 @@ extern int vfs_create(struct inode *, struct dentry *, umode_t, bool); extern int vfs_mkdir(struct inode *, struct dentry *, umode_t); extern int vfs_mknod(struct inode *, struct dentry *, umode_t, dev_t); extern int vfs_symlink(struct inode *, struct dentry *, const char *); -extern int vfs_link(struct dentry *, struct inode *, struct dentry *); +extern int vfs_link(struct dentry *, struct inode *, struct dentry *, struct inode **); extern int vfs_rmdir(struct inode *, struct dentry *); extern int vfs_unlink(struct inode *, struct dentry *, struct inode **); extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, struct inode **); -- cgit v1.2.3-59-g8ed1b From 27ac0ffeac80ba6b9580529568d06144df044366 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Tue, 20 Sep 2011 17:19:26 -0400 Subject: locks: break delegations on any attribute modification NFSv4 uses leases to guarantee that clients can cache metadata as well as data. Cc: Mikulas Patocka Cc: David Howells Cc: Tyler Hicks Cc: Dustin Kirkland Acked-by: Jeff Layton Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- drivers/base/devtmpfs.c | 4 ++-- fs/attr.c | 25 ++++++++++++++++++++++++- fs/cachefiles/interface.c | 4 ++-- fs/ecryptfs/inode.c | 4 ++-- fs/hpfs/namei.c | 2 +- fs/inode.c | 6 +++++- fs/nfsd/vfs.c | 8 ++++++-- fs/open.c | 22 ++++++++++++++++++---- fs/utimes.c | 9 ++++++++- include/linux/fs.h | 2 +- 10 files changed, 69 insertions(+), 17 deletions(-) (limited to 'fs/ecryptfs') diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index 1b8490e2fbde..0f3820121e02 100644 --- a/drivers/base/devtmpfs.c +++ b/drivers/base/devtmpfs.c @@ -216,7 +216,7 @@ static int handle_create(const char *nodename, umode_t mode, kuid_t uid, newattrs.ia_gid = gid; newattrs.ia_valid = ATTR_MODE|ATTR_UID|ATTR_GID; mutex_lock(&dentry->d_inode->i_mutex); - notify_change(dentry, &newattrs); + notify_change(dentry, &newattrs, NULL); mutex_unlock(&dentry->d_inode->i_mutex); /* mark as kernel-created inode */ @@ -322,7 +322,7 @@ static int handle_remove(const char *nodename, struct device *dev) newattrs.ia_valid = ATTR_UID|ATTR_GID|ATTR_MODE; mutex_lock(&dentry->d_inode->i_mutex); - notify_change(dentry, &newattrs); + notify_change(dentry, &newattrs, NULL); mutex_unlock(&dentry->d_inode->i_mutex); err = vfs_unlink(parent.dentry->d_inode, dentry, NULL); if (!err || err == -ENOENT) diff --git a/fs/attr.c b/fs/attr.c index 1449adb14ef6..267968d94673 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -167,7 +167,27 @@ void setattr_copy(struct inode *inode, const struct iattr *attr) } EXPORT_SYMBOL(setattr_copy); -int notify_change(struct dentry * dentry, struct iattr * attr) +/** + * notify_change - modify attributes of a filesytem object + * @dentry: object affected + * @iattr: new attributes + * @delegated_inode: returns inode, if the inode is delegated + * + * The caller must hold the i_mutex on the affected object. + * + * If notify_change discovers a delegation in need of breaking, + * it will return -EWOULDBLOCK and return a reference to the inode in + * delegated_inode. The caller should then break the delegation and + * retry. Because breaking a delegation may take a long time, the + * caller should drop the i_mutex before doing so. + * + * Alternatively, a caller may pass NULL for delegated_inode. This may + * be appropriate for callers that expect the underlying filesystem not + * to be NFS exported. Also, passing NULL is fine for callers holding + * the file open for write, as there can be no conflicting delegation in + * that case. + */ +int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **delegated_inode) { struct inode *inode = dentry->d_inode; umode_t mode = inode->i_mode; @@ -241,6 +261,9 @@ int notify_change(struct dentry * dentry, struct iattr * attr) return 0; error = security_inode_setattr(dentry, attr); + if (error) + return error; + error = try_break_deleg(inode, delegated_inode); if (error) return error; diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c index 43eb5592cdea..5088a418ac4d 100644 --- a/fs/cachefiles/interface.c +++ b/fs/cachefiles/interface.c @@ -449,14 +449,14 @@ static int cachefiles_attr_changed(struct fscache_object *_object) _debug("discard tail %llx", oi_size); newattrs.ia_valid = ATTR_SIZE; newattrs.ia_size = oi_size & PAGE_MASK; - ret = notify_change(object->backer, &newattrs); + ret = notify_change(object->backer, &newattrs, NULL); if (ret < 0) goto truncate_failed; } newattrs.ia_valid = ATTR_SIZE; newattrs.ia_size = ni_size; - ret = notify_change(object->backer, &newattrs); + ret = notify_change(object->backer, &newattrs, NULL); truncate_failed: mutex_unlock(&object->backer->d_inode->i_mutex); diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 1c628f023041..c36c44824471 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -882,7 +882,7 @@ int ecryptfs_truncate(struct dentry *dentry, loff_t new_length) struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry); mutex_lock(&lower_dentry->d_inode->i_mutex); - rc = notify_change(lower_dentry, &lower_ia); + rc = notify_change(lower_dentry, &lower_ia, NULL); mutex_unlock(&lower_dentry->d_inode->i_mutex); } return rc; @@ -983,7 +983,7 @@ static int ecryptfs_setattr(struct dentry *dentry, struct iattr *ia) lower_ia.ia_valid &= ~ATTR_MODE; mutex_lock(&lower_dentry->d_inode->i_mutex); - rc = notify_change(lower_dentry, &lower_ia); + rc = notify_change(lower_dentry, &lower_ia, NULL); mutex_unlock(&lower_dentry->d_inode->i_mutex); out: fsstack_copy_attr_all(inode, lower_inode); diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c index 345713d2f8f3..1b39afdd86fd 100644 --- a/fs/hpfs/namei.c +++ b/fs/hpfs/namei.c @@ -407,7 +407,7 @@ again: /*printk("HPFS: truncating file before delete.\n");*/ newattrs.ia_size = 0; newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME; - err = notify_change(dentry, &newattrs); + err = notify_change(dentry, &newattrs, NULL); put_write_access(inode); if (!err) goto again; diff --git a/fs/inode.c b/fs/inode.c index ce48c359ce9e..4bcdad3c9361 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -1603,7 +1603,11 @@ static int __remove_suid(struct dentry *dentry, int kill) struct iattr newattrs; newattrs.ia_valid = ATTR_FORCE | kill; - return notify_change(dentry, &newattrs); + /* + * Note we call this on write, so notify_change will not + * encounter any conflicting delegations: + */ + return notify_change(dentry, &newattrs, NULL); } int file_remove_suid(struct file *file) diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 27ba21b5f383..94b5f5d2bfed 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -427,7 +427,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap, goto out_nfserr; fh_lock(fhp); - host_err = notify_change(dentry, iap); + host_err = notify_change(dentry, iap, NULL); err = nfserrno(host_err); fh_unlock(fhp); } @@ -988,7 +988,11 @@ static void kill_suid(struct dentry *dentry) ia.ia_valid = ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_PRIV; mutex_lock(&dentry->d_inode->i_mutex); - notify_change(dentry, &ia); + /* + * Note we call this on write, so notify_change will not + * encounter any conflicting delegations: + */ + notify_change(dentry, &ia, NULL); mutex_unlock(&dentry->d_inode->i_mutex); } diff --git a/fs/open.c b/fs/open.c index fffbed40dbe9..4b3e1edf2fe4 100644 --- a/fs/open.c +++ b/fs/open.c @@ -57,7 +57,8 @@ int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs, newattrs.ia_valid |= ret | ATTR_FORCE; mutex_lock(&dentry->d_inode->i_mutex); - ret = notify_change(dentry, &newattrs); + /* Note any delegations or leases have already been broken: */ + ret = notify_change(dentry, &newattrs, NULL); mutex_unlock(&dentry->d_inode->i_mutex); return ret; } @@ -464,21 +465,28 @@ out: static int chmod_common(struct path *path, umode_t mode) { struct inode *inode = path->dentry->d_inode; + struct inode *delegated_inode = NULL; struct iattr newattrs; int error; error = mnt_want_write(path->mnt); if (error) return error; +retry_deleg: mutex_lock(&inode->i_mutex); error = security_path_chmod(path, mode); if (error) goto out_unlock; newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO); newattrs.ia_valid = ATTR_MODE | ATTR_CTIME; - error = notify_change(path->dentry, &newattrs); + error = notify_change(path->dentry, &newattrs, &delegated_inode); out_unlock: mutex_unlock(&inode->i_mutex); + if (delegated_inode) { + error = break_deleg_wait(&delegated_inode); + if (!error) + goto retry_deleg; + } mnt_drop_write(path->mnt); return error; } @@ -522,6 +530,7 @@ SYSCALL_DEFINE2(chmod, const char __user *, filename, umode_t, mode) static int chown_common(struct path *path, uid_t user, gid_t group) { struct inode *inode = path->dentry->d_inode; + struct inode *delegated_inode = NULL; int error; struct iattr newattrs; kuid_t uid; @@ -546,12 +555,17 @@ static int chown_common(struct path *path, uid_t user, gid_t group) if (!S_ISDIR(inode->i_mode)) newattrs.ia_valid |= ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_PRIV; +retry_deleg: mutex_lock(&inode->i_mutex); error = security_path_chown(path, uid, gid); if (!error) - error = notify_change(path->dentry, &newattrs); + error = notify_change(path->dentry, &newattrs, &delegated_inode); mutex_unlock(&inode->i_mutex); - + if (delegated_inode) { + error = break_deleg_wait(&delegated_inode); + if (!error) + goto retry_deleg; + } return error; } diff --git a/fs/utimes.c b/fs/utimes.c index f4fb7eca10e8..aa138d64560a 100644 --- a/fs/utimes.c +++ b/fs/utimes.c @@ -53,6 +53,7 @@ static int utimes_common(struct path *path, struct timespec *times) int error; struct iattr newattrs; struct inode *inode = path->dentry->d_inode; + struct inode *delegated_inode = NULL; error = mnt_want_write(path->mnt); if (error) @@ -101,9 +102,15 @@ static int utimes_common(struct path *path, struct timespec *times) goto mnt_drop_write_and_out; } } +retry_deleg: mutex_lock(&inode->i_mutex); - error = notify_change(path->dentry, &newattrs); + error = notify_change(path->dentry, &newattrs, &delegated_inode); mutex_unlock(&inode->i_mutex); + if (delegated_inode) { + error = break_deleg_wait(&delegated_inode); + if (!error) + goto retry_deleg; + } mnt_drop_write_and_out: mnt_drop_write(path->mnt); diff --git a/include/linux/fs.h b/include/linux/fs.h index 6e36e7118ec1..ab2a0ca82dc5 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2278,7 +2278,7 @@ extern void emergency_remount(void); #ifdef CONFIG_BLOCK extern sector_t bmap(struct inode *, sector_t); #endif -extern int notify_change(struct dentry *, struct iattr *); +extern int notify_change(struct dentry *, struct iattr *, struct inode **); extern int inode_permission(struct inode *, int); extern int generic_permission(struct inode *, int); -- cgit v1.2.3-59-g8ed1b From bdd3536618443809d18868563eeafa63b9d29603 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 9 Nov 2013 22:19:48 -0500 Subject: ecryptfs: ->f_op is never NULL Signed-off-by: Al Viro --- fs/ecryptfs/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'fs/ecryptfs') diff --git a/fs/ecryptfs/file.c b/fs/ecryptfs/file.c index 6b1cd2b0c751..2229a74aeeed 100644 --- a/fs/ecryptfs/file.c +++ b/fs/ecryptfs/file.c @@ -318,7 +318,7 @@ ecryptfs_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) if (ecryptfs_file_to_private(file)) lower_file = ecryptfs_file_to_lower(file); - if (lower_file->f_op && lower_file->f_op->unlocked_ioctl) + if (lower_file->f_op->unlocked_ioctl) rc = lower_file->f_op->unlocked_ioctl(lower_file, cmd, arg); return rc; } -- cgit v1.2.3-59-g8ed1b