diff options
Diffstat (limited to 'fs')
47 files changed, 300 insertions, 169 deletions
diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 5783d0336f96..e47b90e70837 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -645,7 +645,6 @@ error: * @dir: The parent directory * @dentry: The name of file to be created * @mode: The UNIX file mode to set - * @excl: True if the file must not yet exist * * open(.., O_CREAT) is handled in v9fs_vfs_atomic_open(). This is only called * for mknod(2). @@ -654,7 +653,7 @@ error: static int v9fs_vfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dir); u32 perm = unixmode2p9mode(v9ses, mode); diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index f7396d20cb6c..d17c3b6eebb2 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -213,12 +213,11 @@ int v9fs_open_to_dotl_flags(int flags) * @dir: directory inode that is being created * @dentry: dentry that is being deleted * @omode: create permissions - * @excl: True if the file must not yet exist * */ static int v9fs_vfs_create_dotl(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t omode, bool excl) + struct dentry *dentry, umode_t omode) { return v9fs_vfs_mknod_dotl(idmap, dir, dentry, omode, 0); } diff --git a/fs/affs/affs.h b/fs/affs/affs.h index 44a3f69d275f..d6b3393633f2 100644 --- a/fs/affs/affs.h +++ b/fs/affs/affs.h @@ -169,7 +169,7 @@ extern int affs_hash_name(struct super_block *sb, const u8 *name, unsigned int l extern struct dentry *affs_lookup(struct inode *dir, struct dentry *dentry, unsigned int); extern int affs_unlink(struct inode *dir, struct dentry *dentry); extern int affs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool); + struct dentry *dentry, umode_t mode); extern struct dentry *affs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); extern int affs_rmdir(struct inode *dir, struct dentry *dentry); diff --git a/fs/affs/namei.c b/fs/affs/namei.c index c3c6532da4b0..b0001084727a 100644 --- a/fs/affs/namei.c +++ b/fs/affs/namei.c @@ -243,7 +243,7 @@ affs_unlink(struct inode *dir, struct dentry *dentry) int affs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; struct inode *inode; diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 6df56fe9163f..6401fbdd10aa 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -34,7 +34,7 @@ static bool afs_lookup_filldir(struct dir_context *ctx, const char *name, int nl u64 ino, u32 uniquifier); #define AFS_LOOKUP ((filldir_t)0x137UL) static int afs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl); + struct dentry *dentry, umode_t mode); static struct dentry *afs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); static int afs_rmdir(struct inode *dir, struct dentry *dentry); @@ -1633,7 +1633,7 @@ static const struct afs_operation_ops afs_create_operation = { * create a regular file on an AFS filesystem */ static int afs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct afs_operation *op; struct afs_vnode *dvnode = AFS_FS_I(dir); diff --git a/fs/bad_inode.c b/fs/bad_inode.c index acf8613f5e36..486c40f73e51 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -29,7 +29,7 @@ static const struct file_operations bad_file_ops = static int bad_inode_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, bool excl) + umode_t mode) { return -EIO; } diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c index 5b40ab09a796..4c3b4db08cde 100644 --- a/fs/bfs/dir.c +++ b/fs/bfs/dir.c @@ -83,7 +83,7 @@ const struct file_operations bfs_dir_operations = { }; static int bfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { int err; struct inode *inode; diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 2534cd9284d5..910c06297119 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -7007,7 +7007,7 @@ static int btrfs_mknod(struct mnt_idmap *idmap, struct inode *dir, } static int btrfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index ef9e92e362d3..cee70570bd0b 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -983,7 +983,7 @@ out: } static int ceph_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return ceph_mknod(idmap, dir, dentry, mode, 0); } diff --git a/fs/coda/dir.c b/fs/coda/dir.c index 835eb7fdfdad..ea710a5dbbb7 100644 --- a/fs/coda/dir.c +++ b/fs/coda/dir.c @@ -134,7 +134,7 @@ static inline void coda_dir_drop_nlink(struct inode *dir) /* creation routines: create, mknod, mkdir, link, symlink */ static int coda_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *de, umode_t mode, bool excl) + struct dentry *de, umode_t mode) { int error; const char *name=de->d_name.name; diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 7aaf1913f9c6..525297c7ebd8 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -268,7 +268,7 @@ out: static int ecryptfs_create(struct mnt_idmap *idmap, struct inode *directory_inode, struct dentry *ecryptfs_dentry, - umode_t mode, bool excl) + umode_t mode) { struct inode *ecryptfs_inode; int rc; diff --git a/fs/efivarfs/inode.c b/fs/efivarfs/inode.c index 95dcad83da11..f0d009555fc6 100644 --- a/fs/efivarfs/inode.c +++ b/fs/efivarfs/inode.c @@ -75,7 +75,7 @@ static bool efivarfs_valid_name(const char *str, int len) } static int efivarfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode = NULL; struct efivar_entry *var; diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index b7d5e44ad38e..cd9c9eca58f8 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -538,7 +538,7 @@ out: } static int exfat_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; struct inode *inode; diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index 0d09d22fe708..742a78e165d4 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -99,7 +99,7 @@ struct dentry *ext2_get_parent(struct dentry *child) */ static int ext2_create (struct mnt_idmap * idmap, struct inode * dir, struct dentry * dentry, - umode_t mode, bool excl) + umode_t mode) { struct inode *inode; int err; diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index cc49ae04a6f6..c3de64d2a2df 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -2811,7 +2811,7 @@ static int ext4_add_nondir(handle_t *handle, * with d_instantiate(). */ static int ext4_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { handle_t *handle; struct inode *inode; diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index cac03b8e91a1..648681c5ba50 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -366,7 +366,7 @@ fail_drop: } static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct f2fs_sb_info *sbi = F2FS_I_SB(dir); struct f2fs_lock_context lc; diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index c93e05d35ef8..ee6824c5d136 100644 --- a/fs/fat/namei_msdos.c +++ b/fs/fat/namei_msdos.c @@ -265,7 +265,7 @@ static int msdos_add_entry(struct inode *dir, const unsigned char *name, /***** Create a file */ static int msdos_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; struct inode *inode = NULL; diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index e909447873e3..139d3ef4bfae 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -755,7 +755,7 @@ error: } static int vfat_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; struct inode *inode; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 0e2a1039fa43..0efb3141f7f7 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1084,7 +1084,7 @@ static int fuse_mknod(struct mnt_idmap *idmap, struct inode *dir, } static int fuse_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *entry, umode_t mode, bool excl) + struct dentry *entry, umode_t mode) { return fuse_mknod(idmap, dir, entry, mode, 0); } diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 8a77794bbd4a..17bfa283b320 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -963,15 +963,14 @@ fail: * @dir: The directory in which to create the file * @dentry: The dentry of the new file * @mode: The mode of the new file - * @excl: Force fail if inode exists * * Returns: errno */ static int gfs2_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { - return gfs2_create_inode(dir, dentry, NULL, S_IFREG | mode, 0, NULL, 0, excl); + return gfs2_create_inode(dir, dentry, NULL, S_IFREG | mode, 0, NULL, 0, 1); } /** diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c index e13450bb933e..93edc5a80c81 100644 --- a/fs/hfs/dir.c +++ b/fs/hfs/dir.c @@ -184,7 +184,7 @@ static int hfs_dir_release(struct inode *inode, struct file *file) * the directory and the name (and its length) of the new file. */ static int hfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; int res; diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index 8bf6c7cdd9a8..f0aae2cd6fcf 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -562,7 +562,7 @@ out: } static int hfsplus_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); } diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index abe86d72d9ef..7add056d47d8 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -593,7 +593,7 @@ static struct inode *hostfs_iget(struct super_block *sb, char *name) } static int hostfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; char *name; diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c index 353e13a615f5..809113d8248d 100644 --- a/fs/hpfs/namei.c +++ b/fs/hpfs/namei.c @@ -129,7 +129,7 @@ bail: } static int hpfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { const unsigned char *name = dentry->d_name.name; unsigned len = dentry->d_name.len; diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 216e1a0dd0b2..16d8437aed51 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -979,7 +979,7 @@ static struct dentry *hugetlbfs_mkdir(struct mnt_idmap *idmap, struct inode *dir static int hugetlbfs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, bool excl) + umode_t mode) { return hugetlbfs_mknod(idmap, dir, dentry, mode | S_IFREG, 0); } diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c index c4088c3b4ac0..3d4695b838ed 100644 --- a/fs/jffs2/dir.c +++ b/fs/jffs2/dir.c @@ -26,7 +26,7 @@ static int jffs2_readdir (struct file *, struct dir_context *); static int jffs2_create (struct mnt_idmap *, struct inode *, - struct dentry *, umode_t, bool); + struct dentry *, umode_t); static struct dentry *jffs2_lookup (struct inode *,struct dentry *, unsigned int); static int jffs2_link (struct dentry *,struct inode *,struct dentry *); @@ -163,7 +163,7 @@ static int jffs2_readdir(struct file *file, struct dir_context *ctx) static int jffs2_create(struct mnt_idmap *idmap, struct inode *dir_i, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct jffs2_raw_inode *ri; struct jffs2_inode_info *f, *dir_f; diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c index 442d62679262..2cf4e280ee18 100644 --- a/fs/jfs/namei.c +++ b/fs/jfs/namei.c @@ -61,7 +61,7 @@ static inline void free_ea_wmap(struct inode *inode) * */ static int jfs_create(struct mnt_idmap *idmap, struct inode *dip, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { int rc = 0; tid_t tid; /* transaction id */ diff --git a/fs/minix/namei.c b/fs/minix/namei.c index 263e4ba8b1c8..79e591bdfdc1 100644 --- a/fs/minix/namei.c +++ b/fs/minix/namei.c @@ -64,7 +64,7 @@ static int minix_tmpfile(struct mnt_idmap *idmap, struct inode *dir, } static int minix_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return minix_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); } diff --git a/fs/namei.c b/fs/namei.c index 19ce43c9a6e6..9c34304d6671 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4199,7 +4199,7 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode, error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di); if (error) return error; - error = dir->i_op->create(idmap, dir, dentry, mode, true); + error = dir->i_op->create(idmap, dir, dentry, mode); if (!error) fsnotify_create(dir, dentry); return error; @@ -4336,50 +4336,83 @@ static int may_o_create(struct mnt_idmap *idmap, return security_inode_create(dir->dentry->d_inode, dentry, mode); } -/* - * Attempt to atomically look up, create and open a file from a negative - * dentry. - * - * Returns 0 if successful. The file will have been created and attached to - * @file by the filesystem calling finish_open(). - * - * If the file was looked up only or didn't need creating, FMODE_OPENED won't - * be set. The caller will need to perform the open themselves. @path will - * have been updated to point to the new dentry. This may be negative. - * - * Returns an error code otherwise. +/** + * atomic_open() - atomically look up, create and open a file + * @path: parent directory path + * @dentry: child to ->atomic_open() + * @file: file to attach child to + * @open_flag: open flags + * @mode: create mode + * @create_error: return value from may_o_create() + * + * Attempt to look up, create and open @dentry, which must be negative, in a + * single call into the filesystem. + * + * If a non-error dentry is returned then: when FMODE_OPENED is set, + * the file will have been attached to @file by the filesystem calling + * finish_open(). If FMODE_OPENED isn't set, the filesystem instead called + * finish_no_open() and the caller will need to perform the open themselves. + * + * FMODE_CREATED is set when the call to ->atomic_open() actually created + * the file. + * + * Returns: the opened or looked-up dentry, or ERR_PTR() on failure. The + * reference to @dentry is consumed in either case. */ static struct dentry *atomic_open(const struct path *path, struct dentry *dentry, struct file *file, - int open_flag, umode_t mode) + int open_flag, umode_t mode, int create_error) { struct dentry *const DENTRY_NOT_SET = (void *) -1UL; - struct inode *dir = path->dentry->d_inode; + struct inode *dir_inode = path->dentry->d_inode; int error; file->__f_path.dentry = DENTRY_NOT_SET; file->__f_path.mnt = path->mnt; - error = dir->i_op->atomic_open(dir, dentry, file, + error = dir_inode->i_op->atomic_open(dir_inode, dentry, file, open_to_namei_flags(open_flag), mode); d_lookup_done(dentry); + if (!error) { if (file->f_mode & FMODE_OPENED) { - if (unlikely(dentry != file->f_path.dentry)) { + /* finish_open() called */ + struct dentry *opened = file->f_path.dentry; + + if (unlikely(opened != dentry)) { dput(dentry); - dentry = dget(file->f_path.dentry); + dentry = dget(opened); } - } else if (WARN_ON(file->f_path.dentry == DENTRY_NOT_SET)) { - error = -EIO; - } else { - if (file->f_path.dentry) { + } else if (likely(file->f_path.dentry != DENTRY_NOT_SET)) { + /* finish_no_open() called */ + struct dentry *replaced = file->f_path.dentry; + + if (replaced) { dput(dentry); - dentry = file->f_path.dentry; + dentry = replaced; } if (unlikely(d_is_negative(dentry))) error = -ENOENT; + } else { + const char *fsname = dentry->d_sb->s_type->name; + + WARN(1, "%s: ->atomic_open() left file->f_path.dentry unset!\n", + fsname); + error = -EIO; } } + if (error) { + if (unlikely(create_error) && error == -ENOENT) { + /* + * Should have done a create, but errored before. + * Some filesystems return -ENOENT directly instead of + * calling finish_no_open() with a negative dentry; + * either way it should only mean the child doesn't exist, + * so a refused create is safe to record here. + */ + audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); + error = create_error; + } dput(dentry); dentry = ERR_PTR(error); } @@ -4389,32 +4422,52 @@ static struct dentry *atomic_open(const struct path *path, struct dentry *dentry /* * Look up and maybe create and open the last component. * - * Must be called with parent locked (exclusive in O_CREAT case). + * Takes the parent inode lock itself, exclusive if O_CREAT was requested and + * shared otherwise, and drops it again before returning. The caller must not + * hold it. * - * Returns 0 on success, that is, if - * the file was successfully atomically created (if necessary) and opened, or - * the file was not completely opened at this time, though lookups and - * creations were performed. - * These case are distinguished by presence of FMODE_OPENED on file->f_mode. - * In the latter case dentry returned in @path might be negative if O_CREAT - * hadn't been specified. + * On success returns the dentry of the last component. If FMODE_OPENED is set + * on file->f_mode the file was also opened and attached to @file; otherwise + * only lookup and creation were performed and the caller has to open it. In + * the latter case the dentry may be negative if O_CREAT hadn't been specified. * - * An error code is returned on failure. + * Returns ERR_PTR() on failure. */ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, - const struct open_flags *op, - bool got_write, struct delegated_inode *delegated_inode) + const struct open_flags *op) { + struct delegated_inode delegated_inode = { }; struct mnt_idmap *idmap; struct dentry *dir = nd->path.dentry; struct inode *dir_inode = dir->d_inode; - int open_flag = op->open_flag; + int open_flag; struct dentry *dentry; - int error, create_error = 0; - umode_t mode = op->mode; + int error, create_error; + umode_t mode; + bool got_write; - if (unlikely(IS_DEADDIR(dir_inode))) - return ERR_PTR(-ENOENT); +retry: + open_flag = op->open_flag; + got_write = false; + mode = op->mode; + create_error = 0; + + if (open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) { + got_write = !mnt_want_write(nd->path.mnt); + /* + * do _not_ fail yet - we might not need that or fail with + * a different error; we'll be dropping this one anyway. + */ + } + if (open_flag & O_CREAT) + inode_lock(dir_inode); + else + inode_lock_shared(dir_inode); + + if (unlikely(IS_DEADDIR(dir_inode))) { + dentry = ERR_PTR(-ENOENT); + goto out; + } file->f_mode &= ~FMODE_CREATED; dentry = d_lookup(dir, &nd->last); @@ -4422,7 +4475,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, if (!dentry) { dentry = d_alloc_parallel(dir, &nd->last); if (IS_ERR(dentry)) - return dentry; + goto out; } if (d_in_lookup(dentry)) break; @@ -4437,8 +4490,8 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, dentry = NULL; } if (dentry->d_inode) { - /* Cached positive dentry: will open in f_op->open */ - return dentry; + /* Cached positive dentry: will open in do_open(). */ + goto out; } if (open_flag & O_CREAT) @@ -4459,7 +4512,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, if (open_flag & O_CREAT) { if (open_flag & O_EXCL) open_flag &= ~O_TRUNC; - mode = vfs_prepare_mode(idmap, dir->d_inode, mode, mode, mode); + mode = vfs_prepare_mode(idmap, dir_inode, mode, mode, mode); if (likely(got_write)) create_error = may_o_create(idmap, &nd->path, dentry, mode); @@ -4471,10 +4524,9 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, if (dir_inode->i_op->atomic_open) { if (nd->flags & LOOKUP_DIRECTORY) open_flag |= O_DIRECTORY; - dentry = atomic_open(&nd->path, dentry, file, open_flag, mode); - if (unlikely(create_error) && dentry == ERR_PTR(-ENOENT)) - dentry = ERR_PTR(create_error); - return dentry; + dentry = atomic_open(&nd->path, dentry, file, open_flag, mode, + create_error); + goto out; } if (d_in_lookup(dentry)) { @@ -4490,37 +4542,164 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, dentry = res; } } + if (dentry->d_inode || !(op->open_flag & O_CREAT)) { + /* + * No need to create a file. If lookup returned a positive + * dentry, the file will be opened in do_open(). + */ + goto out; + } - /* Negative dentry, just create the file */ - if (!dentry->d_inode && (open_flag & O_CREAT)) { - /* but break the directory lease first! */ - error = try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, delegated_inode); - if (error) - goto out_dput; + /* Negative dentry with O_CREAT flag set */ + audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); - file->f_mode |= FMODE_CREATED; - audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); - if (!dir_inode->i_op->create) { - error = -EACCES; - goto out_dput; - } + if (unlikely(create_error)) { + /* should have done a create, but we already errored */ + error = create_error; + goto out_dput; + } - error = dir_inode->i_op->create(idmap, dir_inode, dentry, - mode, open_flag & O_EXCL); - if (error) - goto out_dput; + error = try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, &delegated_inode); + if (error) + goto out_dput; + + file->f_mode |= FMODE_CREATED; + if (!dir_inode->i_op->create) { + error = -EACCES; + goto out_dput; } - if (unlikely(create_error) && !dentry->d_inode) { - error = create_error; + + error = dir_inode->i_op->create(idmap, dir_inode, dentry, mode); + if (error) goto out_dput; +out: + if (!IS_ERR(dentry)) { + if (file->f_mode & FMODE_CREATED) + fsnotify_create(dir_inode, dentry); + if (file->f_mode & FMODE_OPENED) + fsnotify_open(file); } + if ((open_flag & O_CREAT) || create_error) + inode_unlock(dir_inode); + else + inode_unlock_shared(dir_inode); + + if (got_write) + mnt_drop_write(nd->path.mnt); + + if (is_delegated(&delegated_inode)) { + /* Must have come through out_dput: dentry is an ERR_PTR() */ + error = break_deleg_wait(&delegated_inode); + + if (!error) + goto retry; + dentry = ERR_PTR(error); + } + return dentry; out_dput: dput(dentry); - return ERR_PTR(error); + dentry = ERR_PTR(error); + goto out; } +/** + * vfs_lookup_open - open and possibly create a regular file + * @parent: directory to contain file + * @last: final component of file name + * @open_flag: O_flags + * @mode: initial permissions for file + * + * Open a file after lookup and/or create. This provides similar + * functionality to open_last_lookups() for non-VFS users, particularly + * nfsd. + * It uses ->atomic_open or ->lookup / ->create / ->open as appropriate. + * + * If the fs object found is not a regular file then an error is returned. + * In some cases, related errors are repurposed so that the caller can + * determine the type of file found from the error. + * -EISDIR : a directory was found + * -ELOOP : a symlink was found + * -ENODEV : a block or character device special file was found + * -EFTYPE : any other non-regular file was found, such as FIFO or SOCK. + * or ->atomic_open responded to __O_REGULAR. + * + * Returns: the opened struct file, or an error. + */ +struct file *vfs_lookup_open(struct path *parent, struct qstr *last, + int open_flag, umode_t mode) +{ + struct file *file __free(fput) = NULL; + struct nameidata nd = {}; + struct open_flags op = {}; + struct dentry *dentry; + int error = 0; + + WARN_ONCE(mode & ~S_IALLUGO, "mode must only have permission bits"); + WARN_ONCE(open_flag & ~(O_ACCMODE|O_CREAT|O_EXCL|O_TRUNC|__O_REGULAR), + "open_flag has unsupported flags"); + + mode |= S_IFREG; + open_flag |= __O_REGULAR; + + error = lookup_noperm_common(last, parent->dentry); + if (error) + return ERR_PTR(error); + + file = alloc_empty_file(open_flag, current_cred()); + if (IS_ERR(file)) + return file; + + nd.path = *parent; + nd.last = *last; + nd.flags = LOOKUP_OPEN; + if (open_flag & O_CREAT) { + nd.flags |= LOOKUP_CREATE; + if (open_flag & O_EXCL) + nd.flags |= LOOKUP_EXCL; + } + op.open_flag = open_flag; + op.mode = mode; + dentry = lookup_open(&nd, file, &op); + + if (IS_ERR(dentry)) + return ERR_CAST(dentry); + + if (d_really_is_negative(dentry)) { + error = -ENOENT; + } else if (!(file->f_mode & FMODE_CREATED) && (open_flag & O_EXCL)) { + error = -EEXIST; + } else if ((dentry->d_inode->i_mode & S_IFMT) != S_IFREG) { + switch (dentry->d_inode->i_mode & S_IFMT) { + case S_IFDIR: + error = -EISDIR; + break; + case S_IFLNK: + error = -ELOOP; + break; + case S_IFBLK: + case S_IFCHR: + error = -ENODEV; + break; + case S_IFIFO: + case S_IFSOCK: + default: + error = -EFTYPE; + break; + } + } else if (!(file->f_mode & FMODE_OPENED)) { + nd.path.dentry = dentry; + error = vfs_open(&nd.path, file); + } + dput(dentry); + + if (error) + return ERR_PTR(error); + return no_free_ptr(file); +} +EXPORT_SYMBOL_FOR_MODULES(vfs_lookup_open, "nfsd"); + static inline bool trailing_slashes(struct nameidata *nd) { return (bool)nd->last.name[nd->last.len]; @@ -4560,10 +4739,7 @@ static struct dentry *lookup_fast_for_open(struct nameidata *nd, int open_flag) static const char *open_last_lookups(struct nameidata *nd, struct file *file, const struct open_flags *op) { - struct delegated_inode delegated_inode = { }; - struct dentry *dir = nd->path.dentry; int open_flag = op->open_flag; - bool got_write = false; struct dentry *dentry; const char *res; @@ -4592,44 +4768,10 @@ static const char *open_last_lookups(struct nameidata *nd, return ERR_PTR(-ECHILD); } } -retry: - if (open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) { - got_write = !mnt_want_write(nd->path.mnt); - /* - * do _not_ fail yet - we might not need that or fail with - * a different error; let lookup_open() decide; we'll be - * dropping this one anyway. - */ - } - if (open_flag & O_CREAT) - inode_lock(dir->d_inode); - else - inode_lock_shared(dir->d_inode); - dentry = lookup_open(nd, file, op, got_write, &delegated_inode); - if (!IS_ERR(dentry)) { - if (file->f_mode & FMODE_CREATED) - fsnotify_create(dir->d_inode, dentry); - if (file->f_mode & FMODE_OPENED) - fsnotify_open(file); - } - if (open_flag & O_CREAT) - inode_unlock(dir->d_inode); - else - inode_unlock_shared(dir->d_inode); - - if (got_write) - mnt_drop_write(nd->path.mnt); - - if (IS_ERR(dentry)) { - if (is_delegated(&delegated_inode)) { - int error = break_deleg_wait(&delegated_inode); - if (!error) - goto retry; - return ERR_PTR(error); - } + dentry = lookup_open(nd, file, op); + if (IS_ERR(dentry)) return ERR_CAST(dentry); - } if (file->f_mode & (FMODE_OPENED | FMODE_CREATED)) { dput(nd->path.dentry); @@ -5051,7 +5193,7 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode, /* atomic_open will dput(dentry) on error */ dget(orig_dentry); - dentry = atomic_open(path, dentry, file, flags, mode); + dentry = atomic_open(path, dentry, file, flags, mode, create_error); error = PTR_ERR_OR_ZERO(dentry); if (IS_ERR(dentry)) @@ -5061,9 +5203,6 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode, /* Drop the extra reference */ dput(orig_dentry); - if (unlikely(create_error) && error == -ENOENT) - error = create_error; - if (!error) { if (file->f_mode & FMODE_CREATED) fsnotify_create(dir->d_inode, dentry); diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index c7caffb31935..95c45c8d9114 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2427,9 +2427,9 @@ out_err: } int nfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { - return nfs_do_create(dir, dentry, mode, excl ? O_EXCL : 0); + return nfs_do_create(dir, dentry, mode, O_EXCL); } EXPORT_SYMBOL_GPL(nfs_create); diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index e4533f583632..7f96a258af76 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -395,7 +395,7 @@ extern unsigned long nfs_access_cache_scan(struct shrinker *shrink, struct dentry *nfs_lookup(struct inode *, struct dentry *, unsigned int); void nfs_d_prune_case_insensitive_aliases(struct inode *inode); int nfs_create(struct mnt_idmap *, struct inode *, struct dentry *, - umode_t, bool); + umode_t); struct dentry *nfs_mkdir(struct mnt_idmap *, struct inode *, struct dentry *, umode_t); int nfs_rmdir(struct inode *, struct dentry *); diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c index e2fe95de3d71..0e0a9850ff76 100644 --- a/fs/nilfs2/namei.c +++ b/fs/nilfs2/namei.c @@ -86,7 +86,7 @@ nilfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) * with d_instantiate(). */ static int nilfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; struct nilfs_transaction_info ti; diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index cd403b1d99ee..2e37b2056b11 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -737,7 +737,7 @@ err_out: } static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct ntfs_volume *vol = NTFS_SB(dir->i_sb); struct ntfs_inode *ni; diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index c59de5f2fa97..6d032b22c97d 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -105,7 +105,7 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry, * ntfs_create - inode_operations::create */ static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return ntfs_create_inode(idmap, dir, dentry, NULL, S_IFREG | mode, 0, NULL, 0, NULL); diff --git a/fs/ocfs2/dlmfs/dlmfs.c b/fs/ocfs2/dlmfs/dlmfs.c index 5821e33df78f..f0124f81df29 100644 --- a/fs/ocfs2/dlmfs/dlmfs.c +++ b/fs/ocfs2/dlmfs/dlmfs.c @@ -453,8 +453,7 @@ bail: static int dlmfs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, - bool excl) + umode_t mode) { int status = 0; struct inode *inode; diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 1277666c77cd..12a1fef3ee74 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -667,8 +667,7 @@ static struct dentry *ocfs2_mkdir(struct mnt_idmap *idmap, static int ocfs2_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, - bool excl) + umode_t mode) { int ret; diff --git a/fs/omfs/dir.c b/fs/omfs/dir.c index 2ed541fccf33..a09a98f7e30b 100644 --- a/fs/omfs/dir.c +++ b/fs/omfs/dir.c @@ -286,7 +286,7 @@ static struct dentry *omfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, } static int omfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return omfs_add_node(dir, dentry, mode | S_IFREG); } diff --git a/fs/orangefs/namei.c b/fs/orangefs/namei.c index 75e65e72c2d6..91f97db18971 100644 --- a/fs/orangefs/namei.c +++ b/fs/orangefs/namei.c @@ -18,8 +18,7 @@ static int orangefs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, - bool exclusive) + umode_t mode) { struct orangefs_inode_s *parent = ORANGEFS_I(dir); struct orangefs_kernel_op_s *new_op; diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index a033743dbf51..88bcf98d287d 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -732,7 +732,7 @@ out: } static int ovl_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return ovl_create_object(dentry, (mode & 07777) | S_IFREG, 0, NULL); } diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c index 3987639ed132..0f52ba22aac0 100644 --- a/fs/ramfs/inode.c +++ b/fs/ramfs/inode.c @@ -128,7 +128,7 @@ static struct dentry *ramfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, } static int ramfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return ramfs_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFREG, 0); } diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 854e672a4e37..287c632392c4 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -54,7 +54,7 @@ void cifs_sb_deactive(struct super_block *sb); extern const struct inode_operations cifs_dir_inode_ops; struct inode *cifs_root_iget(struct super_block *sb); int cifs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *direntry, umode_t mode, bool excl); + struct dentry *direntry, umode_t mode); int cifs_atomic_open(struct inode *dir, struct dentry *direntry, struct file *file, unsigned int oflags, umode_t mode); int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 88a4a1787ff0..7803bd5bd01f 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -645,7 +645,7 @@ out_free_xid: * hashed-positive by calling d_instantiate(). */ int cifs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *direntry, umode_t mode, bool excl) + struct dentry *direntry, umode_t mode) { struct cifs_sb_info *cifs_sb = CIFS_SB(dir); int rc; diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index 86d41e077e4d..fd8df10547bf 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -303,7 +303,7 @@ static int ubifs_prepare_create(struct inode *dir, struct dentry *dentry, } static int ubifs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; struct ubifs_info *c = dir->i_sb->s_fs_info; diff --git a/fs/udf/namei.c b/fs/udf/namei.c index 9a3b7cef3606..fd9b6f16f614 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -371,7 +371,7 @@ static int udf_add_nondir(struct dentry *dentry, struct inode *inode) } static int udf_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode = udf_new_inode(dir, mode); diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c index 5b3c85c93242..5012e056200a 100644 --- a/fs/ufs/namei.c +++ b/fs/ufs/namei.c @@ -70,8 +70,7 @@ static struct dentry *ufs_lookup(struct inode * dir, struct dentry *dentry, unsi * with d_instantiate(). */ static int ufs_create (struct mnt_idmap * idmap, - struct inode * dir, struct dentry * dentry, umode_t mode, - bool excl) + struct inode * dir, struct dentry * dentry, umode_t mode) { struct inode *inode; diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c index c5bd3271aa96..0b9eab157432 100644 --- a/fs/vboxsf/dir.c +++ b/fs/vboxsf/dir.c @@ -298,9 +298,9 @@ out: static int vboxsf_dir_mkfile(struct mnt_idmap *idmap, struct inode *parent, struct dentry *dentry, - umode_t mode, bool excl) + umode_t mode) { - return vboxsf_dir_create(parent, dentry, mode, false, excl, NULL); + return vboxsf_dir_create(parent, dentry, mode, false, true, NULL); } static struct dentry *vboxsf_dir_mkdir(struct mnt_idmap *idmap, diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 6339f4956ecb..e48f9e5a1b8a 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -293,8 +293,7 @@ xfs_vn_create( struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, - bool flags) + umode_t mode) { return xfs_generic_create(idmap, dir, dentry, mode, 0, NULL); } @@ -338,7 +337,7 @@ STATIC struct dentry * xfs_vn_ci_lookup( struct inode *dir, struct dentry *dentry, - unsigned int flags) + unsigned int flags) { struct xfs_inode *ip; struct xfs_name xname; |
