From beef5121f3a4d1566c8ab8cd99b4e001862048cf Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:53 +0100 Subject: Revert "af_unix: fix hard linked sockets on overlay" This reverts commit eb0a4a47ae89aaa0674ab3180de6a162f3be2ddf. Since commit 51f7e52dc943 ("ovl: share inode for hard link") there's no need to call d_real_inode() to check two overlay inodes for equality. Side effect of this revert is that it's no longer possible to connect one socket on overlayfs to one on the underlying layer (something which didn't make sense anyway). Signed-off-by: Miklos Szeredi --- net/unix/af_unix.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 2358f2690ec5..6f72508cccb7 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -315,7 +315,7 @@ static struct sock *unix_find_socket_byinode(struct inode *i) &unix_socket_table[i->i_ino & (UNIX_HASH_SIZE - 1)]) { struct dentry *dentry = unix_sk(s)->path.dentry; - if (dentry && d_real_inode(dentry) == i) { + if (dentry && d_backing_inode(dentry) == i) { sock_hold(s); goto found; } @@ -913,7 +913,7 @@ static struct sock *unix_find_other(struct net *net, err = kern_path(sunname->sun_path, LOOKUP_FOLLOW, &path); if (err) goto fail; - inode = d_real_inode(path.dentry); + inode = d_backing_inode(path.dentry); err = inode_permission(inode, MAY_WRITE); if (err) goto put_fail; @@ -1040,7 +1040,7 @@ static int unix_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) goto out_up; } addr->hash = UNIX_HASH_SIZE; - hash = d_real_inode(path.dentry)->i_ino & (UNIX_HASH_SIZE - 1); + hash = d_backing_inode(path.dentry)->i_ino & (UNIX_HASH_SIZE - 1); spin_lock(&unix_table_lock); u->path = path; list = &unix_socket_table[hash]; -- cgit v1.2.3 From 8d3e2936375bacce6abacbce3917d667e4133409 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:54 +0100 Subject: Revert "vfs: rename: check backing inode being equal" This reverts commit 9409e22acdfc9153f88d9b1ed2bd2a5b34d2d3ca. Since commit 51f7e52dc943 ("ovl: share inode for hard link") there's no need to call d_real_inode() to check two overlay inodes for equality. Signed-off-by: Miklos Szeredi --- fs/namei.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 5b4eed221530..31d04d993a2d 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4345,11 +4345,7 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, bool new_is_dir = false; unsigned max_links = new_dir->i_sb->s_max_links; - /* - * Check source == target. - * On overlayfs need to look at underlying inodes. - */ - if (d_real_inode(old_dentry) == d_real_inode(new_dentry)) + if (source == target) return 0; error = may_delete(old_dir, old_dentry, is_dir); -- cgit v1.2.3 From 3616119da484699e7045957c009c13d778563874 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:54 +0100 Subject: vfs: no mnt_want_write_file() in vfs_{copy,clone}_file_range() We've checked for file_out being opened for write. This ensures that we already have mnt_want_write() on target. Signed-off-by: Miklos Szeredi --- fs/read_write.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/fs/read_write.c b/fs/read_write.c index 190e0d362581..3d810a11102c 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -1538,9 +1538,7 @@ ssize_t vfs_copy_file_range(struct file *file_in, loff_t pos_in, if (len == 0) return 0; - ret = mnt_want_write_file(file_out); - if (ret) - return ret; + sb_start_write(inode_out->i_sb); ret = -EOPNOTSUPP; if (file_out->f_op->copy_file_range) @@ -1559,7 +1557,7 @@ ssize_t vfs_copy_file_range(struct file *file_in, loff_t pos_in, inc_syscr(current); inc_syscw(current); - mnt_drop_write_file(file_out); + sb_end_write(inode_out->i_sb); return ret; } @@ -1685,9 +1683,7 @@ int vfs_clone_file_range(struct file *file_in, loff_t pos_in, if (pos_in + len > i_size_read(inode_in)) return -EINVAL; - ret = mnt_want_write_file(file_out); - if (ret) - return ret; + sb_start_write(inode_out->i_sb); ret = file_in->f_op->clone_file_range(file_in, pos_in, file_out, pos_out, len); @@ -1696,7 +1692,7 @@ int vfs_clone_file_range(struct file *file_in, loff_t pos_in, fsnotify_modify(file_out); } - mnt_drop_write_file(file_out); + sb_end_write(inode_out->i_sb); return ret; } EXPORT_SYMBOL(vfs_clone_file_range); -- cgit v1.2.3 From 913b86e92e1f68ab9db00ccb0fecf594732511e5 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Fri, 23 Sep 2016 11:38:10 +0300 Subject: vfs: allow vfs_clone_file_range() across mount points FICLONE/FICLONERANGE ioctls return -EXDEV if src and dest files are not on the same mount point. Practically, clone only requires that src and dest files are on the same file system. Move the check for same mount point to ioctl handler and keep only the check for same super block in the vfs helper. A following patch is going to use the vfs_clone_file_range() helper in overlayfs to copy up between lower and upper mount points on the same file system. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/ioctl.c | 4 ++++ fs/read_write.c | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/ioctl.c b/fs/ioctl.c index c415668c86d4..6715b7208835 100644 --- a/fs/ioctl.c +++ b/fs/ioctl.c @@ -223,7 +223,11 @@ static long ioctl_file_clone(struct file *dst_file, unsigned long srcfd, if (!src_file.file) return -EBADF; + ret = -EXDEV; + if (src_file.file->f_path.mnt != dst_file->f_path.mnt) + goto fdput; ret = vfs_clone_file_range(src_file.file, off, dst_file, destoff, olen); +fdput: fdput(src_file); return ret; } diff --git a/fs/read_write.c b/fs/read_write.c index 3d810a11102c..175d30e3b603 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -1655,8 +1655,12 @@ int vfs_clone_file_range(struct file *file_in, loff_t pos_in, struct inode *inode_out = file_inode(file_out); int ret; - if (inode_in->i_sb != inode_out->i_sb || - file_in->f_path.mnt != file_out->f_path.mnt) + /* + * FICLONE/FICLONERANGE ioctls enforce that src and dest files are on + * the same mount. Practically, they only need to be on the same file + * system. + */ + if (inode_in->i_sb != inode_out->i_sb) return -EXDEV; if (S_ISDIR(inode_in->i_mode) || S_ISDIR(inode_out->i_mode)) -- cgit v1.2.3 From 031a072a0b8ac2646def77aa310a95016c884bb0 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Fri, 23 Sep 2016 11:38:11 +0300 Subject: vfs: call vfs_clone_file_range() under freeze protection Move sb_start_write()/sb_end_write() out of the vfs helper and up into the ioctl handler. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/ioctl.c | 2 +- fs/nfsd/vfs.c | 3 +-- fs/read_write.c | 3 --- include/linux/fs.h | 13 +++++++++++++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/fs/ioctl.c b/fs/ioctl.c index 6715b7208835..cb9b02940805 100644 --- a/fs/ioctl.c +++ b/fs/ioctl.c @@ -226,7 +226,7 @@ static long ioctl_file_clone(struct file *dst_file, unsigned long srcfd, ret = -EXDEV; if (src_file.file->f_path.mnt != dst_file->f_path.mnt) goto fdput; - ret = vfs_clone_file_range(src_file.file, off, dst_file, destoff, olen); + ret = do_clone_file_range(src_file.file, off, dst_file, destoff, olen); fdput: fdput(src_file); return ret; diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 8ca642fe9b21..357e844aee84 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -509,8 +509,7 @@ __be32 nfsd4_set_nfs4_label(struct svc_rqst *rqstp, struct svc_fh *fhp, __be32 nfsd4_clone_file_range(struct file *src, u64 src_pos, struct file *dst, u64 dst_pos, u64 count) { - return nfserrno(vfs_clone_file_range(src, src_pos, dst, dst_pos, - count)); + return nfserrno(do_clone_file_range(src, src_pos, dst, dst_pos, count)); } ssize_t nfsd_copy_file_range(struct file *src, u64 src_pos, struct file *dst, diff --git a/fs/read_write.c b/fs/read_write.c index 175d30e3b603..c4e206b875d0 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -1687,8 +1687,6 @@ int vfs_clone_file_range(struct file *file_in, loff_t pos_in, if (pos_in + len > i_size_read(inode_in)) return -EINVAL; - sb_start_write(inode_out->i_sb); - ret = file_in->f_op->clone_file_range(file_in, pos_in, file_out, pos_out, len); if (!ret) { @@ -1696,7 +1694,6 @@ int vfs_clone_file_range(struct file *file_in, loff_t pos_in, fsnotify_modify(file_out); } - sb_end_write(inode_out->i_sb); return ret; } EXPORT_SYMBOL(vfs_clone_file_range); diff --git a/include/linux/fs.h b/include/linux/fs.h index dc0478c07b2a..52663f1f3084 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1783,6 +1783,19 @@ extern int vfs_clone_file_range(struct file *file_in, loff_t pos_in, extern int vfs_dedupe_file_range(struct file *file, struct file_dedupe_range *same); +static inline int do_clone_file_range(struct file *file_in, loff_t pos_in, + struct file *file_out, loff_t pos_out, + u64 len) +{ + int ret; + + sb_start_write(file_inode(file_out)->i_sb); + ret = vfs_clone_file_range(file_in, pos_in, file_out, pos_out, len); + sb_end_write(file_inode(file_out)->i_sb); + + return ret; +} + struct super_operations { struct inode *(*alloc_inode)(struct super_block *sb); void (*destroy_inode)(struct inode *); -- cgit v1.2.3 From b335e9d9944d9c66cdaadc5e295cc845c31e40a0 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 26 Oct 2016 22:34:01 +0300 Subject: vfs: fix vfs_clone_file_range() for overlayfs files With overlayfs, it is wrong to compare file_inode(inode)->i_sb of regular files with those of non-regular files, because the former reference the real (upper/lower) sb and the latter reference the overlayfs sb. Move the test for same super block after the sanity tests for clone range of directory and non-regular file. This change fixes xfstest generic/157, which returned EXDEV instead of EISDIR/EINVAL in the following test cases over overlayfs: echo "Try to reflink a dir" _reflink_range $testdir1/dir1 0 $testdir1/file2 0 $blksz echo "Try to reflink a device" _reflink_range $testdir1/dev1 0 $testdir1/file2 0 $blksz Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/read_write.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/read_write.c b/fs/read_write.c index c4e206b875d0..53bccd1c786e 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -1655,6 +1655,11 @@ int vfs_clone_file_range(struct file *file_in, loff_t pos_in, struct inode *inode_out = file_inode(file_out); int ret; + if (S_ISDIR(inode_in->i_mode) || S_ISDIR(inode_out->i_mode)) + return -EISDIR; + if (!S_ISREG(inode_in->i_mode) || !S_ISREG(inode_out->i_mode)) + return -EINVAL; + /* * FICLONE/FICLONERANGE ioctls enforce that src and dest files are on * the same mount. Practically, they only need to be on the same file @@ -1663,11 +1668,6 @@ int vfs_clone_file_range(struct file *file_in, loff_t pos_in, if (inode_in->i_sb != inode_out->i_sb) return -EXDEV; - if (S_ISDIR(inode_in->i_mode) || S_ISDIR(inode_out->i_mode)) - return -EISDIR; - if (!S_ISREG(inode_in->i_mode) || !S_ISREG(inode_out->i_mode)) - return -EINVAL; - if (!(file_in->f_mode & FMODE_READ) || !(file_out->f_mode & FMODE_WRITE) || (file_out->f_flags & O_APPEND)) -- cgit v1.2.3 From 2d8f2908e60be605dac89145c3edb5e42631d061 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:54 +0100 Subject: ovl: update doc The quirk for file locks and leases no longer applies. Add missing info about renaming directory residing on lower layer. Signed-off-by: Miklos Szeredi --- Documentation/filesystems/overlayfs.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/filesystems/overlayfs.txt b/Documentation/filesystems/overlayfs.txt index bcbf9710e4af..7aeb8e8d80cf 100644 --- a/Documentation/filesystems/overlayfs.txt +++ b/Documentation/filesystems/overlayfs.txt @@ -185,13 +185,13 @@ filesystem, so both st_dev and st_ino of the file may change. Any open files referring to this inode will access the old data. -Any file locks (and leases) obtained before copy_up will not apply -to the copied up file. - If a file with multiple hard links is copied up, then this will "break" the link. Changes will not be propagated to other names referring to the same inode. +Directory trees are not copied up. If rename(2) is performed on a directory +which is on the lower layer or is merged, then -EXDEV will be returned. + Changes to underlying filesystems --------------------------------- -- cgit v1.2.3 From 31c3a7069593b072bd57192b63b62f9a7e994e9a Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Wed, 12 Oct 2016 16:28:11 +0200 Subject: Revert "ovl: get_write_access() in truncate" This reverts commit 03bea60409328de54e4ff7ec41672e12a9cb0908. Commit 4d0c5ba2ff79 ("vfs: do get_write_access() on upper layer of overlayfs") makes the writecount checks inside overlayfs superfluous, the file is already copied up and write access acquired on the upper inode when ovl_setattr is called with ATTR_SIZE. Signed-off-by: Miklos Szeredi --- fs/overlayfs/inode.c | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 7fb53d055537..a572e38349f6 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -64,27 +64,10 @@ int ovl_setattr(struct dentry *dentry, struct iattr *attr) if (err) goto out; - if (attr->ia_valid & ATTR_SIZE) { - struct inode *realinode = d_inode(ovl_dentry_real(dentry)); - - err = -ETXTBSY; - if (atomic_read(&realinode->i_writecount) < 0) - goto out_drop_write; - } - err = ovl_copy_up(dentry); if (!err) { - struct inode *winode = NULL; - upperdentry = ovl_dentry_upper(dentry); - if (attr->ia_valid & ATTR_SIZE) { - winode = d_inode(upperdentry); - err = get_write_access(winode); - if (err) - goto out_drop_write; - } - if (attr->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID)) attr->ia_valid &= ~ATTR_MODE; @@ -95,11 +78,7 @@ int ovl_setattr(struct dentry *dentry, struct iattr *attr) if (!err) ovl_copyattr(upperdentry->d_inode, dentry->d_inode); inode_unlock(upperdentry->d_inode); - - if (winode) - put_write_access(winode); } -out_drop_write: ovl_drop_write(dentry); out: return err; -- cgit v1.2.3 From 2ea98466491b7609ace297647b07c28d99ef3722 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Fri, 23 Sep 2016 11:38:12 +0300 Subject: ovl: use vfs_clone_file_range() for copy up if possible When copying up within the same fs, try to use vfs_clone_file_range(). This is very efficient when lower and upper are on the same fs with file reflink support. If vfs_clone_file_range() fails for any reason, copy up falls back to the regular data copy code. Tested correct behavior when lower and upper are on: 1. same ext4 (copy) 2. same xfs + reflink patches + mkfs.xfs (copy) 3. same xfs + reflink patches + mkfs.xfs -m reflink=1 (reflink) 4. different xfs + reflink patches + mkfs.xfs -m reflink=1 (copy) For comparison, on my laptop, xfstest overlay/001 (copy up of large sparse files) takes less than 1 second in the xfs reflink setup vs. 25 seconds on the rest of the setups. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 36795eed40b0..f18c1a616e9e 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -153,6 +153,13 @@ static int ovl_copy_up_data(struct path *old, struct path *new, loff_t len) goto out_fput; } + /* Try to use clone_file_range to clone up within the same fs */ + error = vfs_clone_file_range(old_file, 0, new_file, 0, len); + if (!error) + goto out; + /* Couldn't clone, so now we try to copy the data */ + error = 0; + /* FIXME: copy up sparse files efficiently */ while (len) { size_t this_len = OVL_COPY_UP_CHUNK_SIZE; @@ -177,7 +184,7 @@ static int ovl_copy_up_data(struct path *old, struct path *new, loff_t len) len -= bytes; } - +out: if (!error) error = vfs_fsync(new_file, 0); fput(new_file); -- cgit v1.2.3 From 6c02cb59e6fe1dfbe4352dbf089e7a16ef6bfac6 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:55 +0100 Subject: ovl: rename ovl_rename2() to ovl_rename() Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 306b6c161840..617b616ab03b 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -777,9 +777,9 @@ static int ovl_rmdir(struct inode *dir, struct dentry *dentry) return ovl_do_remove(dentry, true); } -static int ovl_rename2(struct inode *olddir, struct dentry *old, - struct inode *newdir, struct dentry *new, - unsigned int flags) +static int ovl_rename(struct inode *olddir, struct dentry *old, + struct inode *newdir, struct dentry *new, + unsigned int flags) { int err; enum ovl_path_type old_type; @@ -1009,7 +1009,7 @@ const struct inode_operations ovl_dir_inode_operations = { .symlink = ovl_symlink, .unlink = ovl_unlink, .rmdir = ovl_rmdir, - .rename = ovl_rename2, + .rename = ovl_rename, .link = ovl_link, .setattr = ovl_setattr, .create = ovl_create, -- cgit v1.2.3 From ca4c8a3a800039c2681d609c5b7491c1bd17c0a7 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:55 +0100 Subject: ovl: treat special files like a regular fs No sense in opening special files on the underlying layers, they work just as well if opened on the overlay. Side effect is that it's no longer possible to connect one side of a pipe opened on overlayfs with the other side opened on the underlying layer. Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 2 +- fs/overlayfs/inode.c | 24 ++++++++++-------------- fs/overlayfs/overlayfs.h | 2 +- fs/overlayfs/super.c | 7 ++++--- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 617b616ab03b..4ef0d539b097 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -537,7 +537,7 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, goto out; err = -ENOMEM; - inode = ovl_new_inode(dentry->d_sb, mode); + inode = ovl_new_inode(dentry->d_sb, mode, rdev); if (!inode) goto out_drop_write; diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index a572e38349f6..a10e948d24fa 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -333,7 +333,7 @@ static const struct inode_operations ovl_symlink_inode_operations = { .update_time = ovl_update_time, }; -static void ovl_fill_inode(struct inode *inode, umode_t mode) +static void ovl_fill_inode(struct inode *inode, umode_t mode, dev_t rdev) { inode->i_ino = get_next_ino(); inode->i_mode = mode; @@ -342,8 +342,11 @@ static void ovl_fill_inode(struct inode *inode, umode_t mode) inode->i_acl = inode->i_default_acl = ACL_DONT_CACHE; #endif - mode &= S_IFMT; - switch (mode) { + switch (mode & S_IFMT) { + case S_IFREG: + inode->i_op = &ovl_file_inode_operations; + break; + case S_IFDIR: inode->i_op = &ovl_dir_inode_operations; inode->i_fop = &ovl_dir_operations; @@ -354,26 +357,19 @@ static void ovl_fill_inode(struct inode *inode, umode_t mode) break; default: - WARN(1, "illegal file type: %i\n", mode); - /* Fall through */ - - case S_IFREG: - case S_IFSOCK: - case S_IFBLK: - case S_IFCHR: - case S_IFIFO: inode->i_op = &ovl_file_inode_operations; + init_special_inode(inode, mode, rdev); break; } } -struct inode *ovl_new_inode(struct super_block *sb, umode_t mode) +struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev) { struct inode *inode; inode = new_inode(sb); if (inode) - ovl_fill_inode(inode, mode); + ovl_fill_inode(inode, mode, rdev); return inode; } @@ -397,7 +393,7 @@ struct inode *ovl_get_inode(struct super_block *sb, struct inode *realinode) inode = iget5_locked(sb, (unsigned long) realinode, ovl_inode_test, ovl_inode_set, realinode); if (inode && inode->i_state & I_NEW) { - ovl_fill_inode(inode, realinode->i_mode); + ovl_fill_inode(inode, realinode->i_mode, realinode->i_rdev); set_nlink(inode, realinode->i_nlink); unlock_new_inode(inode); } diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index e218e741cb99..95d0d86c2d54 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -195,7 +195,7 @@ int ovl_open_maybe_copy_up(struct dentry *dentry, unsigned int file_flags); int ovl_update_time(struct inode *inode, struct timespec *ts, int flags); bool ovl_is_private_xattr(const char *name); -struct inode *ovl_new_inode(struct super_block *sb, umode_t mode); +struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev); struct inode *ovl_get_inode(struct super_block *sb, struct inode *realinode); static inline void ovl_copyattr(struct inode *from, struct inode *to) { diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 0e100856c7b8..e296312005cc 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -304,7 +304,7 @@ static struct dentry *ovl_d_real(struct dentry *dentry, { struct dentry *real; - if (d_is_dir(dentry)) { + if (!d_is_reg(dentry)) { if (!inode || inode == d_inode(dentry)) return dentry; goto bug; @@ -575,7 +575,8 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, if (upperdentry && !d_is_dir(upperdentry)) { inode = ovl_get_inode(dentry->d_sb, realinode); } else { - inode = ovl_new_inode(dentry->d_sb, realinode->i_mode); + inode = ovl_new_inode(dentry->d_sb, realinode->i_mode, + realinode->i_rdev); if (inode) ovl_inode_init(inode, realinode, !!upperdentry); } @@ -1324,7 +1325,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) sb->s_fs_info = ufs; sb->s_flags |= MS_POSIXACL | MS_NOREMOTELOCK; - root_dentry = d_make_root(ovl_new_inode(sb, S_IFDIR)); + root_dentry = d_make_root(ovl_new_inode(sb, S_IFDIR, 0)); if (!root_dentry) goto out_free_oe; -- cgit v1.2.3 From 804032fabb3b5270a7bc355f478eed45b1a5b041 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:55 +0100 Subject: ovl: don't check rename to self This is redundant, the vfs already performed this check (and was broken, see commit 9409e22acdfc ("vfs: rename: check backing inode being equal")). Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 4ef0d539b097..a806fb168ea3 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -826,18 +826,6 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, err = -EXDEV; if (!overwrite && OVL_TYPE_MERGE_OR_LOWER(new_type) && new_is_dir) goto out; - - err = 0; - if (!OVL_TYPE_UPPER(new_type) && !OVL_TYPE_UPPER(old_type)) { - if (ovl_dentry_lower(old)->d_inode == - ovl_dentry_lower(new)->d_inode) - goto out; - } - if (OVL_TYPE_UPPER(new_type) && OVL_TYPE_UPPER(old_type)) { - if (ovl_dentry_upper(old)->d_inode == - ovl_dentry_upper(new)->d_inode) - goto out; - } } else { if (ovl_dentry_is_opaque(new)) new_type = __OVL_PATH_UPPER; @@ -933,6 +921,9 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, if (newdentry == trap) goto out_dput; + if (WARN_ON(olddentry->d_inode == newdentry->d_inode)) + goto out_dput; + if (is_dir && !old_opaque && new_opaque) { err = ovl_set_opaque(olddentry); if (err) -- cgit v1.2.3 From 99f5d08e3640c8319f353e6c883da150c48067f6 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:55 +0100 Subject: ovl: don't check sticky Since commit 07a2daab49c5 ("ovl: Copy up underlying inode's ->i_mode to overlay inode") sticky checking on overlay inode is performed by the vfs, so checking against sticky on underlying inode is not needed. Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index a806fb168ea3..fdc7b14ab571 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -717,28 +717,12 @@ out_unlock: return err; } -static inline int ovl_check_sticky(struct dentry *dentry) -{ - struct inode *dir = ovl_dentry_real(dentry->d_parent)->d_inode; - struct inode *inode = ovl_dentry_real(dentry)->d_inode; - - if (check_sticky(dir, inode)) - return -EPERM; - - return 0; -} - static int ovl_do_remove(struct dentry *dentry, bool is_dir) { enum ovl_path_type type; int err; const struct cred *old_cred; - - err = ovl_check_sticky(dentry); - if (err) - goto out; - err = ovl_want_write(dentry); if (err) goto out; @@ -804,10 +788,6 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, flags &= ~RENAME_NOREPLACE; - err = ovl_check_sticky(old); - if (err) - goto out; - /* Don't copy up directory trees */ old_type = ovl_path_type(old); err = -EXDEV; @@ -815,10 +795,6 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, goto out; if (new->d_inode) { - err = ovl_check_sticky(new); - if (err) - goto out; - if (d_is_dir(new)) new_is_dir = true; -- cgit v1.2.3 From c412ce498396097cb96b3e24e062752312a962c9 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:55 +0100 Subject: ovl: add ovl_dentry_is_whiteout() And use it instead of ovl_dentry_is_opaque() where appropriate. Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 6 +++--- fs/overlayfs/overlayfs.h | 1 + fs/overlayfs/super.c | 5 +++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index fdc7b14ab571..b559221b2d34 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -504,7 +504,7 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, put_cred(override_creds(override_cred)); put_cred(override_cred); - if (!ovl_dentry_is_opaque(dentry)) + if (!ovl_dentry_is_whiteout(dentry)) err = ovl_create_upper(dentry, inode, stat, link, hardlink); else @@ -842,14 +842,14 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, if (overwrite) { if (old_opaque) { - if (new->d_inode || !new_opaque) { + if (!ovl_dentry_is_whiteout(new)) { /* Whiteout source */ flags |= RENAME_WHITEOUT; } else { /* Switch whiteouts */ flags |= RENAME_EXCHANGE; } - } else if (is_dir && !new->d_inode && new_opaque) { + } else if (is_dir && ovl_dentry_is_whiteout(new)) { flags |= RENAME_EXCHANGE; cleanup_whiteout = true; } diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 95d0d86c2d54..51e7d58276ce 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -161,6 +161,7 @@ struct dentry *ovl_workdir(struct dentry *dentry); int ovl_want_write(struct dentry *dentry); void ovl_drop_write(struct dentry *dentry); bool ovl_dentry_is_opaque(struct dentry *dentry); +bool ovl_dentry_is_whiteout(struct dentry *dentry); void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque); bool ovl_is_whiteout(struct dentry *dentry); const struct cred *ovl_override_creds(struct super_block *sb); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index e296312005cc..aa0427dabea8 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -209,6 +209,11 @@ bool ovl_dentry_is_opaque(struct dentry *dentry) return oe->opaque; } +bool ovl_dentry_is_whiteout(struct dentry *dentry) +{ + return !dentry->d_inode && ovl_dentry_is_opaque(dentry); +} + void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque) { struct ovl_entry *oe = dentry->d_fsdata; -- cgit v1.2.3 From 2aff4534b6c48c465c2ba3bca310646652318e16 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:55 +0100 Subject: ovl: check lower existence when removing Currently ovl_lookup() checks existence of lower file even if there's a non-directory on upper (which is always opaque). This is done so that remove can decide whether a whiteout is needed or not. It would be better to defer this check to unlink, since most of the time the gathered information about opaqueness will be unused. This adds a helper ovl_lower_positive() that checks if there's anything on the lower layer(s). The following patches also introduce changes to how the "opaque" attribute is updated on directories: this attribute is added when the directory is creted or moved over a whiteout or object covering something on the lower layer. However following changes will allow the attribute to remain on the directory after being moved, even if the new location doesn't cover anything. Because of this, we need to check lower layers even for opaque directories, so that whiteout is only created when necessary. This function will later be also used to decide about marking a directory opaque, so deal with negative dentries as well. When dealing with negative, it's enough to check for being a whiteout If the dentry is positive but not upper then it also obviously needs whiteout/opaque. Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 4 ++-- fs/overlayfs/overlayfs.h | 1 + fs/overlayfs/super.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index b559221b2d34..4582d5efc01e 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -734,7 +734,7 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir) type = ovl_path_type(dentry); old_cred = ovl_override_creds(dentry->d_sb); - if (OVL_TYPE_PURE_UPPER(type)) + if (!ovl_lower_positive(dentry)) err = ovl_remove_upper(dentry, is_dir); else err = ovl_remove_and_whiteout(dentry, is_dir); @@ -841,7 +841,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, } if (overwrite) { - if (old_opaque) { + if (ovl_lower_positive(old)) { if (!ovl_dentry_is_whiteout(new)) { /* Whiteout source */ flags |= RENAME_WHITEOUT; diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 51e7d58276ce..2bd933aa622b 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -161,6 +161,7 @@ struct dentry *ovl_workdir(struct dentry *dentry); int ovl_want_write(struct dentry *dentry); void ovl_drop_write(struct dentry *dentry); bool ovl_dentry_is_opaque(struct dentry *dentry); +bool ovl_lower_positive(struct dentry *dentry); bool ovl_dentry_is_whiteout(struct dentry *dentry); void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque); bool ovl_is_whiteout(struct dentry *dentry); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index aa0427dabea8..b58710b36157 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -429,7 +429,6 @@ static inline struct dentry *ovl_lookup_real(struct dentry *dir, struct dentry *dentry; dentry = lookup_one_len_unlocked(name->name, dir, name->len); - if (IS_ERR(dentry)) { if (PTR_ERR(dentry) == -ENOENT) dentry = NULL; @@ -613,6 +612,59 @@ out: return ERR_PTR(err); } +bool ovl_lower_positive(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *poe = dentry->d_parent->d_fsdata; + const struct qstr *name = &dentry->d_name; + unsigned int i; + bool positive = false; + bool done = false; + + /* + * If dentry is negative, then lower is positive iff this is a + * whiteout. + */ + if (!dentry->d_inode) + return oe->opaque; + + /* Negative upper -> positive lower */ + if (!oe->__upperdentry) + return true; + + /* Positive upper -> have to look up lower to see whether it exists */ + for (i = 0; !done && !positive && i < poe->numlower; i++) { + struct dentry *this; + struct dentry *lowerdir = poe->lowerstack[i].dentry; + + this = lookup_one_len_unlocked(name->name, lowerdir, + name->len); + if (IS_ERR(this)) { + switch (PTR_ERR(this)) { + case -ENOENT: + case -ENAMETOOLONG: + break; + + default: + /* + * Assume something is there, we just couldn't + * access it. + */ + positive = true; + break; + } + } else { + if (this->d_inode) { + positive = !ovl_is_whiteout(this); + done = true; + } + dput(this); + } + } + + return positive; +} + struct file *ovl_path_open(struct path *path, int flags) { return dentry_open(path, flags | O_NOATIME, current_cred()); -- cgit v1.2.3 From 38e813db61c3951ef76d071ca7d2f46c2e939b80 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:55 +0100 Subject: ovl: get rid of PURE type The remainging uses of __OVL_PATH_PURE can be replaced by ovl_dentry_is_opaque(). Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 9 +++------ fs/overlayfs/overlayfs.h | 6 ++---- fs/overlayfs/super.c | 4 +--- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 4582d5efc01e..76b1edccbedd 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -803,10 +803,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, if (!overwrite && OVL_TYPE_MERGE_OR_LOWER(new_type) && new_is_dir) goto out; } else { - if (ovl_dentry_is_opaque(new)) - new_type = __OVL_PATH_UPPER; - else - new_type = __OVL_PATH_UPPER | __OVL_PATH_PURE; + new_type = __OVL_PATH_UPPER; } err = ovl_want_write(old); @@ -826,8 +823,8 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, goto out_drop_write; } - old_opaque = !OVL_TYPE_PURE_UPPER(old_type); - new_opaque = !OVL_TYPE_PURE_UPPER(new_type); + old_opaque = ovl_dentry_is_opaque(old); + new_opaque = ovl_dentry_is_opaque(new); old_cred = ovl_override_creds(old->d_sb); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 2bd933aa622b..f183d1db78bd 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -12,14 +12,12 @@ struct ovl_entry; enum ovl_path_type { - __OVL_PATH_PURE = (1 << 0), - __OVL_PATH_UPPER = (1 << 1), - __OVL_PATH_MERGE = (1 << 2), + __OVL_PATH_UPPER = (1 << 0), + __OVL_PATH_MERGE = (1 << 1), }; #define OVL_TYPE_UPPER(type) ((type) & __OVL_PATH_UPPER) #define OVL_TYPE_MERGE(type) ((type) & __OVL_PATH_MERGE) -#define OVL_TYPE_PURE_UPPER(type) ((type) & __OVL_PATH_PURE) #define OVL_TYPE_MERGE_OR_LOWER(type) \ (OVL_TYPE_MERGE(type) || !OVL_TYPE_UPPER(type)) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index b58710b36157..c0463fb80f41 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -80,12 +80,10 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry) /* * Non-dir dentry can hold lower dentry from previous - * location. Its purity depends only on opaque flag. + * location. */ if (oe->numlower && S_ISDIR(dentry->d_inode->i_mode)) type |= __OVL_PATH_MERGE; - else if (!oe->opaque) - type |= __OVL_PATH_PURE; } else { if (oe->numlower > 1) type |= __OVL_PATH_MERGE; -- cgit v1.2.3 From 370e55ace59c2d3ed8f0ca933155030b9652e04f Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:55 +0100 Subject: ovl: rename: simplify handling of lower/merged directory d_is_dir() is safe to call on a negative dentry. Use this fact to simplify handling of the lower or merged directories. Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 30 ++++++++++++------------------ fs/overlayfs/overlayfs.h | 3 --- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 76b1edccbedd..69c3971a9992 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -761,13 +761,18 @@ static int ovl_rmdir(struct inode *dir, struct dentry *dentry) return ovl_do_remove(dentry, true); } +static bool ovl_type_merge_or_lower(struct dentry *dentry) +{ + enum ovl_path_type type = ovl_path_type(dentry); + + return OVL_TYPE_MERGE(type) || !OVL_TYPE_UPPER(type); +} + static int ovl_rename(struct inode *olddir, struct dentry *old, struct inode *newdir, struct dentry *new, unsigned int flags) { int err; - enum ovl_path_type old_type; - enum ovl_path_type new_type; struct dentry *old_upperdir; struct dentry *new_upperdir; struct dentry *olddentry; @@ -778,7 +783,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, bool cleanup_whiteout = false; bool overwrite = !(flags & RENAME_EXCHANGE); bool is_dir = d_is_dir(old); - bool new_is_dir = false; + bool new_is_dir = d_is_dir(new); struct dentry *opaquedir = NULL; const struct cred *old_cred = NULL; @@ -789,22 +794,11 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, flags &= ~RENAME_NOREPLACE; /* Don't copy up directory trees */ - old_type = ovl_path_type(old); err = -EXDEV; - if (OVL_TYPE_MERGE_OR_LOWER(old_type) && is_dir) + if (is_dir && ovl_type_merge_or_lower(old)) + goto out; + if (!overwrite && new_is_dir && ovl_type_merge_or_lower(new)) goto out; - - if (new->d_inode) { - if (d_is_dir(new)) - new_is_dir = true; - - new_type = ovl_path_type(new); - err = -EXDEV; - if (!overwrite && OVL_TYPE_MERGE_OR_LOWER(new_type) && new_is_dir) - goto out; - } else { - new_type = __OVL_PATH_UPPER; - } err = ovl_want_write(old); if (err) @@ -828,7 +822,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, old_cred = ovl_override_creds(old->d_sb); - if (overwrite && OVL_TYPE_MERGE_OR_LOWER(new_type) && new_is_dir) { + if (overwrite && new_is_dir && ovl_type_merge_or_lower(new)) { opaquedir = ovl_check_empty_and_clear(new); err = PTR_ERR(opaquedir); if (IS_ERR(opaquedir)) { diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index f183d1db78bd..db28512165c5 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -18,9 +18,6 @@ enum ovl_path_type { #define OVL_TYPE_UPPER(type) ((type) & __OVL_PATH_UPPER) #define OVL_TYPE_MERGE(type) ((type) & __OVL_PATH_MERGE) -#define OVL_TYPE_MERGE_OR_LOWER(type) \ - (OVL_TYPE_MERGE(type) || !OVL_TYPE_UPPER(type)) - #define OVL_XATTR_PREFIX XATTR_TRUSTED_PREFIX "overlay." #define OVL_XATTR_OPAQUE OVL_XATTR_PREFIX "opaque" -- cgit v1.2.3 From 3ee23ff1025a18796607cf4110a8ffa8e2d163fd Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:55 +0100 Subject: ovl: check lower existence of rename target Check if something exists on the lower layer(s) under the target or rename to decide if directory needs to be marked "opaque". Marking opaque is done before the rename, and on failure the marking was undone. Also the opaque xattr was removed if the target didn't cover anything. This patch changes behavior so that removal of "opaque" is not done in either of the above cases. This means that directory may have the opaque flag even if it doesn't cover anything. However this shouldn't affect the performance or semantics of the overalay, while simplifying the code. Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 63 ++++++++++-------------------------------------------- 1 file changed, 11 insertions(+), 52 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 69c3971a9992..f24b6b967901 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -126,17 +126,6 @@ static int ovl_set_opaque(struct dentry *upperdentry) return ovl_do_setxattr(upperdentry, OVL_XATTR_OPAQUE, "y", 1, 0); } -static void ovl_remove_opaque(struct dentry *upperdentry) -{ - int err; - - err = ovl_do_removexattr(upperdentry, OVL_XATTR_OPAQUE); - if (err) { - pr_warn("overlayfs: failed to remove opaque from '%s' (%i)\n", - upperdentry->d_name.name, err); - } -} - static int ovl_dir_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) { @@ -817,9 +806,6 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, goto out_drop_write; } - old_opaque = ovl_dentry_is_opaque(old); - new_opaque = ovl_dentry_is_opaque(new); - old_cred = ovl_override_creds(old->d_sb); if (overwrite && new_is_dir && ovl_type_merge_or_lower(new)) { @@ -868,6 +854,9 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, if (IS_ERR(newdentry)) goto out_dput_old; + old_opaque = ovl_dentry_is_opaque(old); + new_opaque = ovl_dentry_is_opaque(new); + err = -ESTALE; if (ovl_dentry_upper(new)) { if (opaquedir) { @@ -891,54 +880,24 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, if (WARN_ON(olddentry->d_inode == newdentry->d_inode)) goto out_dput; - if (is_dir && !old_opaque && new_opaque) { + if (is_dir && !old_opaque && ovl_lower_positive(new)) { err = ovl_set_opaque(olddentry); if (err) goto out_dput; + ovl_dentry_set_opaque(old, true); } - if (!overwrite && new_is_dir && old_opaque && !new_opaque) { + if (!overwrite && + new_is_dir && !new_opaque && ovl_lower_positive(old)) { err = ovl_set_opaque(newdentry); if (err) goto out_dput; + ovl_dentry_set_opaque(new, true); } - if (old_opaque || new_opaque) { - err = ovl_do_rename(old_upperdir->d_inode, olddentry, - new_upperdir->d_inode, newdentry, - flags); - } else { - /* No debug for the plain case */ - BUG_ON(flags & ~RENAME_EXCHANGE); - err = vfs_rename(old_upperdir->d_inode, olddentry, - new_upperdir->d_inode, newdentry, - NULL, flags); - } - - if (err) { - if (is_dir && !old_opaque && new_opaque) - ovl_remove_opaque(olddentry); - if (!overwrite && new_is_dir && old_opaque && !new_opaque) - ovl_remove_opaque(newdentry); + err = ovl_do_rename(old_upperdir->d_inode, olddentry, + new_upperdir->d_inode, newdentry, flags); + if (err) goto out_dput; - } - - if (is_dir && old_opaque && !new_opaque) - ovl_remove_opaque(olddentry); - if (!overwrite && new_is_dir && !old_opaque && new_opaque) - ovl_remove_opaque(newdentry); - - /* - * Old dentry now lives in different location. Dentries in - * lowerstack are stale. We cannot drop them here because - * access to them is lockless. This could be only pure upper - * or opaque directory - numlower is zero. Or upper non-dir - * entry - its pureness is tracked by flag opaque. - */ - if (old_opaque != new_opaque) { - ovl_dentry_set_opaque(old, new_opaque); - if (!overwrite) - ovl_dentry_set_opaque(new, old_opaque); - } if (cleanup_whiteout) ovl_cleanup(old_upperdir->d_inode, newdentry); -- cgit v1.2.3 From 8ee6059c58ea525f76b4efb98f8f66845f697efc Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:56 +0100 Subject: ovl: simplify lookup If encountering a non-directory, then stop looking at lower layers. In this case the oe->opaque flag is not set anymore, which doesn't matter since existence of lower file is now checked at remove/rename time. Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 54 ++++++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index c0463fb80f41..a19fbcde16bd 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -473,7 +473,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int ctr = 0; struct inode *inode = NULL; bool upperopaque = false; - struct dentry *this, *prev = NULL; + bool stop = false; + bool isdir = false; + struct dentry *this; unsigned int i; int err; @@ -494,23 +496,26 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, if (ovl_is_whiteout(this)) { dput(this); this = NULL; - upperopaque = true; - } else if (poe->numlower && ovl_is_opaquedir(this)) { - upperopaque = true; + stop = upperopaque = true; + } else if (!d_is_dir(this)) { + stop = true; + } else { + isdir = true; + if (poe->numlower && ovl_is_opaquedir(this)) + stop = upperopaque = true; } } - upperdentry = prev = this; + upperdentry = this; } - if (!upperopaque && poe->numlower) { + if (!stop && poe->numlower) { err = -ENOMEM; stack = kcalloc(poe->numlower, sizeof(struct path), GFP_KERNEL); if (!stack) goto out_put_upper; } - for (i = 0; !upperopaque && i < poe->numlower; i++) { - bool opaque = false; + for (i = 0; !stop && i < poe->numlower; i++) { struct path lowerpath = poe->lowerstack[i]; this = ovl_lookup_real(lowerpath.dentry, &dentry->d_name); @@ -530,35 +535,26 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, break; } /* - * Only makes sense to check opaque dir if this is not the - * lowermost layer. + * If this is a non-directory then stop here. */ - if (i < poe->numlower - 1 && ovl_is_opaquedir(this)) - opaque = true; - - if (prev && (!S_ISDIR(prev->d_inode->i_mode) || - !S_ISDIR(this->d_inode->i_mode))) { + if (!d_is_dir(this)) { + if (isdir) { + dput(this); + break; + } + stop = true; + } else { /* - * FIXME: check for upper-opaqueness maybe better done - * in remove code. + * Only makes sense to check opaque dir if this is not + * the lowermost layer. */ - if (prev == upperdentry) - upperopaque = true; - dput(this); - break; + if (i < poe->numlower - 1 && ovl_is_opaquedir(this)) + stop = true; } - /* - * If this is a non-directory then stop here. - */ - if (!S_ISDIR(this->d_inode->i_mode)) - opaque = true; stack[ctr].dentry = this; stack[ctr].mnt = lowerpath.mnt; ctr++; - prev = this; - if (opaque) - break; } oe = ovl_alloc_entry(ctr); -- cgit v1.2.3 From 2b8c30e9ef1492c34099b97365115504f6cd6995 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:56 +0100 Subject: ovl: use d_is_dir() Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index a19fbcde16bd..212e746320b3 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -82,7 +82,7 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry) * Non-dir dentry can hold lower dentry from previous * location. */ - if (oe->numlower && S_ISDIR(dentry->d_inode->i_mode)) + if (oe->numlower && d_is_dir(dentry)) type |= __OVL_PATH_MERGE; } else { if (oe->numlower > 1) @@ -955,7 +955,7 @@ static int ovl_mount_dir_noesc(const char *name, struct path *path) pr_err("overlayfs: filesystem on '%s' not supported\n", name); goto out_put; } - if (!S_ISDIR(path->dentry->d_inode->i_mode)) { + if (!d_is_dir(path->dentry)) { pr_err("overlayfs: '%s' not a directory\n", name); goto out_put; } -- cgit v1.2.3 From bbb1e54dd53cf83863e856dd5518ce5e58791115 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:56 +0100 Subject: ovl: split super.c fs/overlayfs/super.c is the biggest of the overlayfs source files and it contains various utility functions as well as the rather complicated lookup code. Split these parts out to separate files. Before: 1446 fs/overlayfs/super.c After: 919 fs/overlayfs/super.c 267 fs/overlayfs/namei.c 235 fs/overlayfs/util.c 51 fs/overlayfs/ovl_entry.h Signed-off-by: Miklos Szeredi --- fs/overlayfs/Makefile | 2 +- fs/overlayfs/namei.c | 267 ++++++++++++++++++++++++ fs/overlayfs/overlayfs.h | 34 +-- fs/overlayfs/ovl_entry.h | 51 +++++ fs/overlayfs/super.c | 529 +---------------------------------------------- fs/overlayfs/util.c | 235 +++++++++++++++++++++ 6 files changed, 572 insertions(+), 546 deletions(-) create mode 100644 fs/overlayfs/namei.c create mode 100644 fs/overlayfs/ovl_entry.h create mode 100644 fs/overlayfs/util.c diff --git a/fs/overlayfs/Makefile b/fs/overlayfs/Makefile index 900daed3e91d..99373bbc1478 100644 --- a/fs/overlayfs/Makefile +++ b/fs/overlayfs/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_OVERLAY_FS) += overlay.o -overlay-objs := super.o inode.o dir.o readdir.o copy_up.o +overlay-objs := super.o namei.o util.o inode.o dir.o readdir.o copy_up.o diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c new file mode 100644 index 000000000000..f4057fcb0246 --- /dev/null +++ b/fs/overlayfs/namei.c @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2011 Novell Inc. + * Copyright (C) 2016 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include +#include +#include +#include "overlayfs.h" +#include "ovl_entry.h" + +static struct dentry *ovl_lookup_real(struct dentry *dir, + const struct qstr *name) +{ + struct dentry *dentry; + + dentry = lookup_one_len_unlocked(name->name, dir, name->len); + if (IS_ERR(dentry)) { + if (PTR_ERR(dentry) == -ENOENT) + dentry = NULL; + } else if (!dentry->d_inode) { + dput(dentry); + dentry = NULL; + } else if (ovl_dentry_weird(dentry)) { + dput(dentry); + /* Don't support traversing automounts and other weirdness */ + dentry = ERR_PTR(-EREMOTE); + } + return dentry; +} + +static bool ovl_is_opaquedir(struct dentry *dentry) +{ + int res; + char val; + + if (!d_is_dir(dentry)) + return false; + + res = vfs_getxattr(dentry, OVL_XATTR_OPAQUE, &val, 1); + if (res == 1 && val == 'y') + return true; + + return false; +} + +/* + * Returns next layer in stack starting from top. + * Returns -1 if this is the last layer. + */ +int ovl_path_next(int idx, struct dentry *dentry, struct path *path) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + BUG_ON(idx < 0); + if (idx == 0) { + ovl_path_upper(dentry, path); + if (path->dentry) + return oe->numlower ? 1 : -1; + idx++; + } + BUG_ON(idx > oe->numlower); + *path = oe->lowerstack[idx - 1]; + + return (idx < oe->numlower) ? idx + 1 : -1; +} + +struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + struct ovl_entry *oe; + const struct cred *old_cred; + struct ovl_entry *poe = dentry->d_parent->d_fsdata; + struct path *stack = NULL; + struct dentry *upperdir, *upperdentry = NULL; + unsigned int ctr = 0; + struct inode *inode = NULL; + bool upperopaque = false; + bool stop = false; + bool isdir = false; + struct dentry *this; + unsigned int i; + int err; + + old_cred = ovl_override_creds(dentry->d_sb); + upperdir = ovl_upperdentry_dereference(poe); + if (upperdir) { + this = ovl_lookup_real(upperdir, &dentry->d_name); + err = PTR_ERR(this); + if (IS_ERR(this)) + goto out; + + if (this) { + if (unlikely(ovl_dentry_remote(this))) { + dput(this); + err = -EREMOTE; + goto out; + } + if (ovl_is_whiteout(this)) { + dput(this); + this = NULL; + stop = upperopaque = true; + } else if (!d_is_dir(this)) { + stop = true; + } else { + isdir = true; + if (poe->numlower && ovl_is_opaquedir(this)) + stop = upperopaque = true; + } + } + upperdentry = this; + } + + if (!stop && poe->numlower) { + err = -ENOMEM; + stack = kcalloc(poe->numlower, sizeof(struct path), GFP_KERNEL); + if (!stack) + goto out_put_upper; + } + + for (i = 0; !stop && i < poe->numlower; i++) { + struct path lowerpath = poe->lowerstack[i]; + + this = ovl_lookup_real(lowerpath.dentry, &dentry->d_name); + err = PTR_ERR(this); + if (IS_ERR(this)) { + /* + * If it's positive, then treat ENAMETOOLONG as ENOENT. + */ + if (err == -ENAMETOOLONG && (upperdentry || ctr)) + continue; + goto out_put; + } + if (!this) + continue; + if (ovl_is_whiteout(this)) { + dput(this); + break; + } + /* + * If this is a non-directory then stop here. + */ + if (!d_is_dir(this)) { + if (isdir) { + dput(this); + break; + } + stop = true; + } else { + /* + * Only makes sense to check opaque dir if this is not + * the lowermost layer. + */ + if (i < poe->numlower - 1 && ovl_is_opaquedir(this)) + stop = true; + } + + stack[ctr].dentry = this; + stack[ctr].mnt = lowerpath.mnt; + ctr++; + } + + oe = ovl_alloc_entry(ctr); + err = -ENOMEM; + if (!oe) + goto out_put; + + if (upperdentry || ctr) { + struct dentry *realdentry; + struct inode *realinode; + + realdentry = upperdentry ? upperdentry : stack[0].dentry; + realinode = d_inode(realdentry); + + err = -ENOMEM; + if (upperdentry && !d_is_dir(upperdentry)) { + inode = ovl_get_inode(dentry->d_sb, realinode); + } else { + inode = ovl_new_inode(dentry->d_sb, realinode->i_mode, + realinode->i_rdev); + if (inode) + ovl_inode_init(inode, realinode, !!upperdentry); + } + if (!inode) + goto out_free_oe; + ovl_copyattr(realdentry->d_inode, inode); + } + + revert_creds(old_cred); + oe->opaque = upperopaque; + oe->__upperdentry = upperdentry; + memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); + kfree(stack); + dentry->d_fsdata = oe; + d_add(dentry, inode); + + return NULL; + +out_free_oe: + kfree(oe); +out_put: + for (i = 0; i < ctr; i++) + dput(stack[i].dentry); + kfree(stack); +out_put_upper: + dput(upperdentry); +out: + revert_creds(old_cred); + return ERR_PTR(err); +} + +bool ovl_lower_positive(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *poe = dentry->d_parent->d_fsdata; + const struct qstr *name = &dentry->d_name; + unsigned int i; + bool positive = false; + bool done = false; + + /* + * If dentry is negative, then lower is positive iff this is a + * whiteout. + */ + if (!dentry->d_inode) + return oe->opaque; + + /* Negative upper -> positive lower */ + if (!oe->__upperdentry) + return true; + + /* Positive upper -> have to look up lower to see whether it exists */ + for (i = 0; !done && !positive && i < poe->numlower; i++) { + struct dentry *this; + struct dentry *lowerdir = poe->lowerstack[i].dentry; + + this = lookup_one_len_unlocked(name->name, lowerdir, + name->len); + if (IS_ERR(this)) { + switch (PTR_ERR(this)) { + case -ENOENT: + case -ENAMETOOLONG: + break; + + default: + /* + * Assume something is there, we just couldn't + * access it. + */ + positive = true; + break; + } + } else { + if (this->d_inode) { + positive = !ovl_is_whiteout(this); + done = true; + } + dput(this); + } + } + + return positive; +} diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index db28512165c5..f6e4d3539a25 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -9,8 +9,6 @@ #include -struct ovl_entry; - enum ovl_path_type { __OVL_PATH_UPPER = (1 << 0), __OVL_PATH_MERGE = (1 << 1), @@ -138,37 +136,39 @@ static inline struct inode *ovl_inode_real(struct inode *inode, bool *is_upper) return (struct inode *) (x & ~OVL_ISUPPER_MASK); } +/* util.c */ +int ovl_want_write(struct dentry *dentry); +void ovl_drop_write(struct dentry *dentry); +struct dentry *ovl_workdir(struct dentry *dentry); +const struct cred *ovl_override_creds(struct super_block *sb); +struct ovl_entry *ovl_alloc_entry(unsigned int numlower); +bool ovl_dentry_remote(struct dentry *dentry); +bool ovl_dentry_weird(struct dentry *dentry); enum ovl_path_type ovl_path_type(struct dentry *dentry); -u64 ovl_dentry_version_get(struct dentry *dentry); -void ovl_dentry_version_inc(struct dentry *dentry); void ovl_path_upper(struct dentry *dentry, struct path *path); void ovl_path_lower(struct dentry *dentry, struct path *path); enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path); -int ovl_path_next(int idx, struct dentry *dentry, struct path *path); struct dentry *ovl_dentry_upper(struct dentry *dentry); struct dentry *ovl_dentry_lower(struct dentry *dentry); struct dentry *ovl_dentry_real(struct dentry *dentry); -struct vfsmount *ovl_entry_mnt_real(struct ovl_entry *oe, struct inode *inode, - bool is_upper); struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry); void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache); -struct dentry *ovl_workdir(struct dentry *dentry); -int ovl_want_write(struct dentry *dentry); -void ovl_drop_write(struct dentry *dentry); bool ovl_dentry_is_opaque(struct dentry *dentry); -bool ovl_lower_positive(struct dentry *dentry); bool ovl_dentry_is_whiteout(struct dentry *dentry); void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque); -bool ovl_is_whiteout(struct dentry *dentry); -const struct cred *ovl_override_creds(struct super_block *sb); void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry); +void ovl_inode_init(struct inode *inode, struct inode *realinode, + bool is_upper); void ovl_inode_update(struct inode *inode, struct inode *upperinode); -struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, - unsigned int flags); +void ovl_dentry_version_inc(struct dentry *dentry); +u64 ovl_dentry_version_get(struct dentry *dentry); +bool ovl_is_whiteout(struct dentry *dentry); struct file *ovl_path_open(struct path *path, int flags); -struct dentry *ovl_upper_create(struct dentry *upperdir, struct dentry *dentry, - struct kstat *stat, const char *link); +/* namei.c */ +int ovl_path_next(int idx, struct dentry *dentry, struct path *path); +struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags); +bool ovl_lower_positive(struct dentry *dentry); /* readdir.c */ extern const struct file_operations ovl_dir_operations; diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h new file mode 100644 index 000000000000..3b7ba59ad27e --- /dev/null +++ b/fs/overlayfs/ovl_entry.h @@ -0,0 +1,51 @@ +/* + * + * Copyright (C) 2011 Novell Inc. + * Copyright (C) 2016 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +struct ovl_config { + char *lowerdir; + char *upperdir; + char *workdir; + bool default_permissions; +}; + +/* private information held for overlayfs's superblock */ +struct ovl_fs { + struct vfsmount *upper_mnt; + unsigned numlower; + struct vfsmount **lower_mnt; + struct dentry *workdir; + long lower_namelen; + /* pathnames of lower and upper dirs, for show_options */ + struct ovl_config config; + /* creds of process who forced instantiation of super block */ + const struct cred *creator_cred; +}; + +/* private information held for every overlayfs dentry */ +struct ovl_entry { + struct dentry *__upperdentry; + struct ovl_dir_cache *cache; + union { + struct { + u64 version; + bool opaque; + }; + struct rcu_head rcu; + }; + unsigned numlower; + struct path lowerstack[]; +}; + +struct ovl_entry *ovl_alloc_entry(unsigned int numlower); + +static inline struct dentry *ovl_upperdentry_dereference(struct ovl_entry *oe) +{ + return lockless_dereference(oe->__upperdentry); +} diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 212e746320b3..011482e74096 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -9,283 +9,25 @@ #include #include -#include #include -#include #include -#include #include #include -#include #include #include #include #include "overlayfs.h" +#include "ovl_entry.h" MODULE_AUTHOR("Miklos Szeredi "); MODULE_DESCRIPTION("Overlay filesystem"); MODULE_LICENSE("GPL"); -struct ovl_config { - char *lowerdir; - char *upperdir; - char *workdir; - bool default_permissions; -}; - -/* private information held for overlayfs's superblock */ -struct ovl_fs { - struct vfsmount *upper_mnt; - unsigned numlower; - struct vfsmount **lower_mnt; - struct dentry *workdir; - long lower_namelen; - /* pathnames of lower and upper dirs, for show_options */ - struct ovl_config config; - /* creds of process who forced instantiation of super block */ - const struct cred *creator_cred; -}; struct ovl_dir_cache; -/* private information held for every overlayfs dentry */ -struct ovl_entry { - struct dentry *__upperdentry; - struct ovl_dir_cache *cache; - union { - struct { - u64 version; - bool opaque; - }; - struct rcu_head rcu; - }; - unsigned numlower; - struct path lowerstack[]; -}; - #define OVL_MAX_STACK 500 -static struct dentry *__ovl_dentry_lower(struct ovl_entry *oe) -{ - return oe->numlower ? oe->lowerstack[0].dentry : NULL; -} - -enum ovl_path_type ovl_path_type(struct dentry *dentry) -{ - struct ovl_entry *oe = dentry->d_fsdata; - enum ovl_path_type type = 0; - - if (oe->__upperdentry) { - type = __OVL_PATH_UPPER; - - /* - * Non-dir dentry can hold lower dentry from previous - * location. - */ - if (oe->numlower && d_is_dir(dentry)) - type |= __OVL_PATH_MERGE; - } else { - if (oe->numlower > 1) - type |= __OVL_PATH_MERGE; - } - return type; -} - -static struct dentry *ovl_upperdentry_dereference(struct ovl_entry *oe) -{ - return lockless_dereference(oe->__upperdentry); -} - -void ovl_path_upper(struct dentry *dentry, struct path *path) -{ - struct ovl_fs *ofs = dentry->d_sb->s_fs_info; - struct ovl_entry *oe = dentry->d_fsdata; - - path->mnt = ofs->upper_mnt; - path->dentry = ovl_upperdentry_dereference(oe); -} - -enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path) -{ - enum ovl_path_type type = ovl_path_type(dentry); - - if (!OVL_TYPE_UPPER(type)) - ovl_path_lower(dentry, path); - else - ovl_path_upper(dentry, path); - - return type; -} - -struct dentry *ovl_dentry_upper(struct dentry *dentry) -{ - struct ovl_entry *oe = dentry->d_fsdata; - - return ovl_upperdentry_dereference(oe); -} - -struct dentry *ovl_dentry_lower(struct dentry *dentry) -{ - struct ovl_entry *oe = dentry->d_fsdata; - - return __ovl_dentry_lower(oe); -} - -struct dentry *ovl_dentry_real(struct dentry *dentry) -{ - struct ovl_entry *oe = dentry->d_fsdata; - struct dentry *realdentry; - - realdentry = ovl_upperdentry_dereference(oe); - if (!realdentry) - realdentry = __ovl_dentry_lower(oe); - - return realdentry; -} - -static void ovl_inode_init(struct inode *inode, struct inode *realinode, - bool is_upper) -{ - WRITE_ONCE(inode->i_private, (unsigned long) realinode | - (is_upper ? OVL_ISUPPER_MASK : 0)); -} - -struct vfsmount *ovl_entry_mnt_real(struct ovl_entry *oe, struct inode *inode, - bool is_upper) -{ - if (is_upper) { - struct ovl_fs *ofs = inode->i_sb->s_fs_info; - - return ofs->upper_mnt; - } else { - return oe->numlower ? oe->lowerstack[0].mnt : NULL; - } -} - -struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry) -{ - struct ovl_entry *oe = dentry->d_fsdata; - - return oe->cache; -} - -void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache) -{ - struct ovl_entry *oe = dentry->d_fsdata; - - oe->cache = cache; -} - -void ovl_path_lower(struct dentry *dentry, struct path *path) -{ - struct ovl_entry *oe = dentry->d_fsdata; - - *path = oe->numlower ? oe->lowerstack[0] : (struct path) { NULL, NULL }; -} - -int ovl_want_write(struct dentry *dentry) -{ - struct ovl_fs *ofs = dentry->d_sb->s_fs_info; - return mnt_want_write(ofs->upper_mnt); -} - -void ovl_drop_write(struct dentry *dentry) -{ - struct ovl_fs *ofs = dentry->d_sb->s_fs_info; - mnt_drop_write(ofs->upper_mnt); -} - -struct dentry *ovl_workdir(struct dentry *dentry) -{ - struct ovl_fs *ofs = dentry->d_sb->s_fs_info; - return ofs->workdir; -} - -bool ovl_dentry_is_opaque(struct dentry *dentry) -{ - struct ovl_entry *oe = dentry->d_fsdata; - return oe->opaque; -} - -bool ovl_dentry_is_whiteout(struct dentry *dentry) -{ - return !dentry->d_inode && ovl_dentry_is_opaque(dentry); -} - -void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque) -{ - struct ovl_entry *oe = dentry->d_fsdata; - oe->opaque = opaque; -} - -void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry) -{ - struct ovl_entry *oe = dentry->d_fsdata; - - WARN_ON(!inode_is_locked(upperdentry->d_parent->d_inode)); - WARN_ON(oe->__upperdentry); - /* - * Make sure upperdentry is consistent before making it visible to - * ovl_upperdentry_dereference(). - */ - smp_wmb(); - oe->__upperdentry = upperdentry; -} - -void ovl_inode_update(struct inode *inode, struct inode *upperinode) -{ - WARN_ON(!upperinode); - WARN_ON(!inode_unhashed(inode)); - WRITE_ONCE(inode->i_private, - (unsigned long) upperinode | OVL_ISUPPER_MASK); - if (!S_ISDIR(upperinode->i_mode)) - __insert_inode_hash(inode, (unsigned long) upperinode); -} - -void ovl_dentry_version_inc(struct dentry *dentry) -{ - struct ovl_entry *oe = dentry->d_fsdata; - - WARN_ON(!inode_is_locked(dentry->d_inode)); - oe->version++; -} - -u64 ovl_dentry_version_get(struct dentry *dentry) -{ - struct ovl_entry *oe = dentry->d_fsdata; - - WARN_ON(!inode_is_locked(dentry->d_inode)); - return oe->version; -} - -bool ovl_is_whiteout(struct dentry *dentry) -{ - struct inode *inode = dentry->d_inode; - - return inode && IS_WHITEOUT(inode); -} - -const struct cred *ovl_override_creds(struct super_block *sb) -{ - struct ovl_fs *ofs = sb->s_fs_info; - - return override_creds(ofs->creator_cred); -} - -static bool ovl_is_opaquedir(struct dentry *dentry) -{ - int res; - char val; - - if (!d_is_dir(dentry)) - return false; - - res = vfs_getxattr(dentry, OVL_XATTR_OPAQUE, &val, 1); - if (res == 1 && val == 'y') - return true; - - return false; -} static void ovl_dentry_release(struct dentry *dentry) { @@ -395,275 +137,6 @@ static const struct dentry_operations ovl_reval_dentry_operations = { .d_weak_revalidate = ovl_dentry_weak_revalidate, }; -static struct ovl_entry *ovl_alloc_entry(unsigned int numlower) -{ - size_t size = offsetof(struct ovl_entry, lowerstack[numlower]); - struct ovl_entry *oe = kzalloc(size, GFP_KERNEL); - - if (oe) - oe->numlower = numlower; - - return oe; -} - -static bool ovl_dentry_remote(struct dentry *dentry) -{ - return dentry->d_flags & - (DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE | - DCACHE_OP_REAL); -} - -static bool ovl_dentry_weird(struct dentry *dentry) -{ - return dentry->d_flags & (DCACHE_NEED_AUTOMOUNT | - DCACHE_MANAGE_TRANSIT | - DCACHE_OP_HASH | - DCACHE_OP_COMPARE); -} - -static inline struct dentry *ovl_lookup_real(struct dentry *dir, - const struct qstr *name) -{ - struct dentry *dentry; - - dentry = lookup_one_len_unlocked(name->name, dir, name->len); - if (IS_ERR(dentry)) { - if (PTR_ERR(dentry) == -ENOENT) - dentry = NULL; - } else if (!dentry->d_inode) { - dput(dentry); - dentry = NULL; - } else if (ovl_dentry_weird(dentry)) { - dput(dentry); - /* Don't support traversing automounts and other weirdness */ - dentry = ERR_PTR(-EREMOTE); - } - return dentry; -} - -/* - * Returns next layer in stack starting from top. - * Returns -1 if this is the last layer. - */ -int ovl_path_next(int idx, struct dentry *dentry, struct path *path) -{ - struct ovl_entry *oe = dentry->d_fsdata; - - BUG_ON(idx < 0); - if (idx == 0) { - ovl_path_upper(dentry, path); - if (path->dentry) - return oe->numlower ? 1 : -1; - idx++; - } - BUG_ON(idx > oe->numlower); - *path = oe->lowerstack[idx - 1]; - - return (idx < oe->numlower) ? idx + 1 : -1; -} - -struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, - unsigned int flags) -{ - struct ovl_entry *oe; - const struct cred *old_cred; - struct ovl_entry *poe = dentry->d_parent->d_fsdata; - struct path *stack = NULL; - struct dentry *upperdir, *upperdentry = NULL; - unsigned int ctr = 0; - struct inode *inode = NULL; - bool upperopaque = false; - bool stop = false; - bool isdir = false; - struct dentry *this; - unsigned int i; - int err; - - old_cred = ovl_override_creds(dentry->d_sb); - upperdir = ovl_upperdentry_dereference(poe); - if (upperdir) { - this = ovl_lookup_real(upperdir, &dentry->d_name); - err = PTR_ERR(this); - if (IS_ERR(this)) - goto out; - - if (this) { - if (unlikely(ovl_dentry_remote(this))) { - dput(this); - err = -EREMOTE; - goto out; - } - if (ovl_is_whiteout(this)) { - dput(this); - this = NULL; - stop = upperopaque = true; - } else if (!d_is_dir(this)) { - stop = true; - } else { - isdir = true; - if (poe->numlower && ovl_is_opaquedir(this)) - stop = upperopaque = true; - } - } - upperdentry = this; - } - - if (!stop && poe->numlower) { - err = -ENOMEM; - stack = kcalloc(poe->numlower, sizeof(struct path), GFP_KERNEL); - if (!stack) - goto out_put_upper; - } - - for (i = 0; !stop && i < poe->numlower; i++) { - struct path lowerpath = poe->lowerstack[i]; - - this = ovl_lookup_real(lowerpath.dentry, &dentry->d_name); - err = PTR_ERR(this); - if (IS_ERR(this)) { - /* - * If it's positive, then treat ENAMETOOLONG as ENOENT. - */ - if (err == -ENAMETOOLONG && (upperdentry || ctr)) - continue; - goto out_put; - } - if (!this) - continue; - if (ovl_is_whiteout(this)) { - dput(this); - break; - } - /* - * If this is a non-directory then stop here. - */ - if (!d_is_dir(this)) { - if (isdir) { - dput(this); - break; - } - stop = true; - } else { - /* - * Only makes sense to check opaque dir if this is not - * the lowermost layer. - */ - if (i < poe->numlower - 1 && ovl_is_opaquedir(this)) - stop = true; - } - - stack[ctr].dentry = this; - stack[ctr].mnt = lowerpath.mnt; - ctr++; - } - - oe = ovl_alloc_entry(ctr); - err = -ENOMEM; - if (!oe) - goto out_put; - - if (upperdentry || ctr) { - struct dentry *realdentry; - struct inode *realinode; - - realdentry = upperdentry ? upperdentry : stack[0].dentry; - realinode = d_inode(realdentry); - - err = -ENOMEM; - if (upperdentry && !d_is_dir(upperdentry)) { - inode = ovl_get_inode(dentry->d_sb, realinode); - } else { - inode = ovl_new_inode(dentry->d_sb, realinode->i_mode, - realinode->i_rdev); - if (inode) - ovl_inode_init(inode, realinode, !!upperdentry); - } - if (!inode) - goto out_free_oe; - ovl_copyattr(realdentry->d_inode, inode); - } - - revert_creds(old_cred); - oe->opaque = upperopaque; - oe->__upperdentry = upperdentry; - memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); - kfree(stack); - dentry->d_fsdata = oe; - d_add(dentry, inode); - - return NULL; - -out_free_oe: - kfree(oe); -out_put: - for (i = 0; i < ctr; i++) - dput(stack[i].dentry); - kfree(stack); -out_put_upper: - dput(upperdentry); -out: - revert_creds(old_cred); - return ERR_PTR(err); -} - -bool ovl_lower_positive(struct dentry *dentry) -{ - struct ovl_entry *oe = dentry->d_fsdata; - struct ovl_entry *poe = dentry->d_parent->d_fsdata; - const struct qstr *name = &dentry->d_name; - unsigned int i; - bool positive = false; - bool done = false; - - /* - * If dentry is negative, then lower is positive iff this is a - * whiteout. - */ - if (!dentry->d_inode) - return oe->opaque; - - /* Negative upper -> positive lower */ - if (!oe->__upperdentry) - return true; - - /* Positive upper -> have to look up lower to see whether it exists */ - for (i = 0; !done && !positive && i < poe->numlower; i++) { - struct dentry *this; - struct dentry *lowerdir = poe->lowerstack[i].dentry; - - this = lookup_one_len_unlocked(name->name, lowerdir, - name->len); - if (IS_ERR(this)) { - switch (PTR_ERR(this)) { - case -ENOENT: - case -ENAMETOOLONG: - break; - - default: - /* - * Assume something is there, we just couldn't - * access it. - */ - positive = true; - break; - } - } else { - if (this->d_inode) { - positive = !ovl_is_whiteout(this); - done = true; - } - dput(this); - } - } - - return positive; -} - -struct file *ovl_path_open(struct path *path, int flags) -{ - return dentry_open(path, flags | O_NOATIME, current_cred()); -} - static void ovl_put_super(struct super_block *sb) { struct ovl_fs *ufs = sb->s_fs_info; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c new file mode 100644 index 000000000000..0d45a84468d2 --- /dev/null +++ b/fs/overlayfs/util.c @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2011 Novell Inc. + * Copyright (C) 2016 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include +#include +#include +#include +#include "overlayfs.h" +#include "ovl_entry.h" + +int ovl_want_write(struct dentry *dentry) +{ + struct ovl_fs *ofs = dentry->d_sb->s_fs_info; + return mnt_want_write(ofs->upper_mnt); +} + +void ovl_drop_write(struct dentry *dentry) +{ + struct ovl_fs *ofs = dentry->d_sb->s_fs_info; + mnt_drop_write(ofs->upper_mnt); +} + +struct dentry *ovl_workdir(struct dentry *dentry) +{ + struct ovl_fs *ofs = dentry->d_sb->s_fs_info; + return ofs->workdir; +} + +const struct cred *ovl_override_creds(struct super_block *sb) +{ + struct ovl_fs *ofs = sb->s_fs_info; + + return override_creds(ofs->creator_cred); +} + +struct ovl_entry *ovl_alloc_entry(unsigned int numlower) +{ + size_t size = offsetof(struct ovl_entry, lowerstack[numlower]); + struct ovl_entry *oe = kzalloc(size, GFP_KERNEL); + + if (oe) + oe->numlower = numlower; + + return oe; +} + +bool ovl_dentry_remote(struct dentry *dentry) +{ + return dentry->d_flags & + (DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE | + DCACHE_OP_REAL); +} + +bool ovl_dentry_weird(struct dentry *dentry) +{ + return dentry->d_flags & (DCACHE_NEED_AUTOMOUNT | + DCACHE_MANAGE_TRANSIT | + DCACHE_OP_HASH | + DCACHE_OP_COMPARE); +} + +enum ovl_path_type ovl_path_type(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + enum ovl_path_type type = 0; + + if (oe->__upperdentry) { + type = __OVL_PATH_UPPER; + + /* + * Non-dir dentry can hold lower dentry from previous + * location. + */ + if (oe->numlower && d_is_dir(dentry)) + type |= __OVL_PATH_MERGE; + } else { + if (oe->numlower > 1) + type |= __OVL_PATH_MERGE; + } + return type; +} + +void ovl_path_upper(struct dentry *dentry, struct path *path) +{ + struct ovl_fs *ofs = dentry->d_sb->s_fs_info; + struct ovl_entry *oe = dentry->d_fsdata; + + path->mnt = ofs->upper_mnt; + path->dentry = ovl_upperdentry_dereference(oe); +} + +void ovl_path_lower(struct dentry *dentry, struct path *path) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + *path = oe->numlower ? oe->lowerstack[0] : (struct path) { NULL, NULL }; +} + +enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path) +{ + enum ovl_path_type type = ovl_path_type(dentry); + + if (!OVL_TYPE_UPPER(type)) + ovl_path_lower(dentry, path); + else + ovl_path_upper(dentry, path); + + return type; +} + +struct dentry *ovl_dentry_upper(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + return ovl_upperdentry_dereference(oe); +} + +static struct dentry *__ovl_dentry_lower(struct ovl_entry *oe) +{ + return oe->numlower ? oe->lowerstack[0].dentry : NULL; +} + +struct dentry *ovl_dentry_lower(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + return __ovl_dentry_lower(oe); +} + +struct dentry *ovl_dentry_real(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + struct dentry *realdentry; + + realdentry = ovl_upperdentry_dereference(oe); + if (!realdentry) + realdentry = __ovl_dentry_lower(oe); + + return realdentry; +} + +struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + return oe->cache; +} + +void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + oe->cache = cache; +} + +bool ovl_dentry_is_opaque(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + return oe->opaque; +} + +bool ovl_dentry_is_whiteout(struct dentry *dentry) +{ + return !dentry->d_inode && ovl_dentry_is_opaque(dentry); +} + +void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque) +{ + struct ovl_entry *oe = dentry->d_fsdata; + oe->opaque = opaque; +} + +void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + WARN_ON(!inode_is_locked(upperdentry->d_parent->d_inode)); + WARN_ON(oe->__upperdentry); + /* + * Make sure upperdentry is consistent before making it visible to + * ovl_upperdentry_dereference(). + */ + smp_wmb(); + oe->__upperdentry = upperdentry; +} + +void ovl_inode_init(struct inode *inode, struct inode *realinode, bool is_upper) +{ + WRITE_ONCE(inode->i_private, (unsigned long) realinode | + (is_upper ? OVL_ISUPPER_MASK : 0)); +} + +void ovl_inode_update(struct inode *inode, struct inode *upperinode) +{ + WARN_ON(!upperinode); + WARN_ON(!inode_unhashed(inode)); + WRITE_ONCE(inode->i_private, + (unsigned long) upperinode | OVL_ISUPPER_MASK); + if (!S_ISDIR(upperinode->i_mode)) + __insert_inode_hash(inode, (unsigned long) upperinode); +} + +void ovl_dentry_version_inc(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + WARN_ON(!inode_is_locked(dentry->d_inode)); + oe->version++; +} + +u64 ovl_dentry_version_get(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + WARN_ON(!inode_is_locked(dentry->d_inode)); + return oe->version; +} + +bool ovl_is_whiteout(struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + + return inode && IS_WHITEOUT(inode); +} + +struct file *ovl_path_open(struct path *path, int flags) +{ + return dentry_open(path, flags | O_NOATIME, current_cred()); +} -- cgit v1.2.3 From 6b2d5fe46fa8f4fc1c5262c73930b9a2a94db2e3 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:56 +0100 Subject: ovl: check namelen We already calculate f_namelen in statfs as the maximum of the name lengths provided by the filesystems taking part in the overlay. Signed-off-by: Miklos Szeredi --- fs/overlayfs/namei.c | 16 ++++++++-------- fs/overlayfs/ovl_entry.h | 2 +- fs/overlayfs/super.c | 38 ++++++++++++++++++++++++++------------ 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index f4057fcb0246..2e0b84c68ef6 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -20,7 +20,8 @@ static struct dentry *ovl_lookup_real(struct dentry *dir, dentry = lookup_one_len_unlocked(name->name, dir, name->len); if (IS_ERR(dentry)) { - if (PTR_ERR(dentry) == -ENOENT) + if (PTR_ERR(dentry) == -ENOENT || + PTR_ERR(dentry) == -ENAMETOOLONG) dentry = NULL; } else if (!dentry->d_inode) { dput(dentry); @@ -74,6 +75,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, { struct ovl_entry *oe; const struct cred *old_cred; + struct ovl_fs *ofs = dentry->d_sb->s_fs_info; struct ovl_entry *poe = dentry->d_parent->d_fsdata; struct path *stack = NULL; struct dentry *upperdir, *upperdentry = NULL; @@ -86,6 +88,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int i; int err; + if (dentry->d_name.len > ofs->namelen) + return ERR_PTR(-ENAMETOOLONG); + old_cred = ovl_override_creds(dentry->d_sb); upperdir = ovl_upperdentry_dereference(poe); if (upperdir) { @@ -127,14 +132,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, this = ovl_lookup_real(lowerpath.dentry, &dentry->d_name); err = PTR_ERR(this); - if (IS_ERR(this)) { - /* - * If it's positive, then treat ENAMETOOLONG as ENOENT. - */ - if (err == -ENAMETOOLONG && (upperdentry || ctr)) - continue; + if (IS_ERR(this)) goto out_put; - } + if (!this) continue; if (ovl_is_whiteout(this)) { diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 3b7ba59ad27e..b10745edfc93 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -21,7 +21,7 @@ struct ovl_fs { unsigned numlower; struct vfsmount **lower_mnt; struct dentry *workdir; - long lower_namelen; + long namelen; /* pathnames of lower and upper dirs, for show_options */ struct ovl_config config; /* creds of process who forced instantiation of super block */ diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 011482e74096..e19e2ed6a4fd 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -174,7 +174,7 @@ static int ovl_statfs(struct dentry *dentry, struct kstatfs *buf) err = vfs_statfs(&path, buf); if (!err) { - buf->f_namelen = max(buf->f_namelen, ofs->lower_namelen); + buf->f_namelen = ofs->namelen; buf->f_type = OVERLAYFS_SUPER_MAGIC; } @@ -461,22 +461,33 @@ static int ovl_mount_dir(const char *name, struct path *path) return err; } -static int ovl_lower_dir(const char *name, struct path *path, long *namelen, - int *stack_depth, bool *remote) +static int ovl_check_namelen(struct path *path, struct ovl_fs *ofs, + const char *name) { - int err; struct kstatfs statfs; + int err = vfs_statfs(path, &statfs); + + if (err) + pr_err("overlayfs: statfs failed on '%s'\n", name); + else + ofs->namelen = max(ofs->namelen, statfs.f_namelen); + + return err; +} + +static int ovl_lower_dir(const char *name, struct path *path, + struct ovl_fs *ofs, int *stack_depth, bool *remote) +{ + int err; err = ovl_mount_dir_noesc(name, path); if (err) goto out; - err = vfs_statfs(path, &statfs); - if (err) { - pr_err("overlayfs: statfs failed on '%s'\n", name); + err = ovl_check_namelen(path, ofs, name); + if (err) goto out_put; - } - *namelen = max(*namelen, statfs.f_namelen); + *stack_depth = max(*stack_depth, path->mnt->mnt_sb->s_stack_depth); if (ovl_dentry_remote(path->dentry)) @@ -708,6 +719,10 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) goto out_put_upperpath; } + err = ovl_check_namelen(&upperpath, ufs, ufs->config.upperdir); + if (err) + goto out_put_upperpath; + err = ovl_mount_dir(ufs->config.workdir, &workpath); if (err) goto out_put_upperpath; @@ -745,9 +760,8 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) lower = lowertmp; for (numlower = 0; numlower < stacklen; numlower++) { - err = ovl_lower_dir(lower, &stack[numlower], - &ufs->lower_namelen, &sb->s_stack_depth, - &remote); + err = ovl_lower_dir(lower, &stack[numlower], ufs, + &sb->s_stack_depth, &remote); if (err) goto out_put_lowerpath; -- cgit v1.2.3 From 48fab5d7c750ff70aa77c36a44c01211020bbc98 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 16 Nov 2016 11:22:39 +0200 Subject: ovl: fix nested overlayfs mount When the upper overlayfs checks "trusted.overlay.*" xattr on the underlying overlayfs mount, it gets -EPERM, which confuses the upper overlayfs. Fix this by returning -EOPNOTSUPP instead of -EPERM from ovl_own_xattr_get() and ovl_own_xattr_set(). This behavior is consistent with the behavior of ovl_listxattr(), which filters out the private overlayfs xattrs. Note: nested overlays are deprecated. But this change makes sense regardless: these xattrs are private to the overlay and should always be hidden. Hence getting and setting them should indicate this. [SzMi: Use EOPNOTSUPP instead of ENODATA and use it for both getting and setting "trusted.overlay." xattrs. This is a perfectly valid error code for "we don't support this prefix", which is the case here.] Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index e19e2ed6a4fd..aadb25413e6e 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -603,7 +603,7 @@ static int ovl_own_xattr_get(const struct xattr_handler *handler, struct dentry *dentry, struct inode *inode, const char *name, void *buffer, size_t size) { - return -EPERM; + return -EOPNOTSUPP; } static int ovl_own_xattr_set(const struct xattr_handler *handler, @@ -611,7 +611,7 @@ static int ovl_own_xattr_set(const struct xattr_handler *handler, const char *name, const void *value, size_t size, int flags) { - return -EPERM; + return -EOPNOTSUPP; } static int ovl_other_xattr_get(const struct xattr_handler *handler, -- cgit v1.2.3 From e28edc46b8e29d2a4c10263cd7769e657582fff4 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:56 +0100 Subject: ovl: consolidate lookup for underlying layers Use a common helper for lookup of upper and lower layers. This paves the way for looking up directory redirects. No functional change. Signed-off-by: Miklos Szeredi --- fs/overlayfs/namei.c | 156 ++++++++++++++++++++++++++++----------------------- 1 file changed, 86 insertions(+), 70 deletions(-) diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 2e0b84c68ef6..f213297d187e 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -13,26 +13,13 @@ #include "overlayfs.h" #include "ovl_entry.h" -static struct dentry *ovl_lookup_real(struct dentry *dir, - const struct qstr *name) -{ - struct dentry *dentry; - - dentry = lookup_one_len_unlocked(name->name, dir, name->len); - if (IS_ERR(dentry)) { - if (PTR_ERR(dentry) == -ENOENT || - PTR_ERR(dentry) == -ENAMETOOLONG) - dentry = NULL; - } else if (!dentry->d_inode) { - dput(dentry); - dentry = NULL; - } else if (ovl_dentry_weird(dentry)) { - dput(dentry); - /* Don't support traversing automounts and other weirdness */ - dentry = ERR_PTR(-EREMOTE); - } - return dentry; -} +struct ovl_lookup_data { + struct qstr name; + bool is_dir; + bool opaque; + bool stop; + bool last; +}; static bool ovl_is_opaquedir(struct dentry *dentry) { @@ -49,6 +36,64 @@ static bool ovl_is_opaquedir(struct dentry *dentry) return false; } +static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d, + const char *name, unsigned int namelen, + struct dentry **ret) +{ + struct dentry *this; + int err; + + this = lookup_one_len_unlocked(name, base, namelen); + if (IS_ERR(this)) { + err = PTR_ERR(this); + this = NULL; + if (err == -ENOENT || err == -ENAMETOOLONG) + goto out; + goto out_err; + } + if (!this->d_inode) + goto put_and_out; + + if (ovl_dentry_weird(this)) { + /* Don't support traversing automounts and other weirdness */ + err = -EREMOTE; + goto out_err; + } + if (ovl_is_whiteout(this)) { + d->stop = d->opaque = true; + goto put_and_out; + } + if (!d_can_lookup(this)) { + d->stop = true; + if (d->is_dir) + goto put_and_out; + goto out; + } + d->is_dir = true; + if (!d->last && ovl_is_opaquedir(this)) { + d->stop = d->opaque = true; + goto out; + } +out: + *ret = this; + return 0; + +put_and_out: + dput(this); + this = NULL; + goto out; + +out_err: + dput(this); + return err; +} + +static int ovl_lookup_layer(struct dentry *base, struct ovl_lookup_data *d, + struct dentry **ret) +{ + return ovl_lookup_single(base, d, d->name.name, d->name.len, ret); +} + /* * Returns next layer in stack starting from top. * Returns -1 if this is the last layer. @@ -82,11 +127,16 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int ctr = 0; struct inode *inode = NULL; bool upperopaque = false; - bool stop = false; - bool isdir = false; struct dentry *this; unsigned int i; int err; + struct ovl_lookup_data d = { + .name = dentry->d_name, + .is_dir = false, + .opaque = false, + .stop = false, + .last = !poe->numlower, + }; if (dentry->d_name.len > ofs->namelen) return ERR_PTR(-ENAMETOOLONG); @@ -94,70 +144,36 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, old_cred = ovl_override_creds(dentry->d_sb); upperdir = ovl_upperdentry_dereference(poe); if (upperdir) { - this = ovl_lookup_real(upperdir, &dentry->d_name); - err = PTR_ERR(this); - if (IS_ERR(this)) + err = ovl_lookup_layer(upperdir, &d, &upperdentry); + if (err) goto out; - if (this) { - if (unlikely(ovl_dentry_remote(this))) { - dput(this); - err = -EREMOTE; - goto out; - } - if (ovl_is_whiteout(this)) { - dput(this); - this = NULL; - stop = upperopaque = true; - } else if (!d_is_dir(this)) { - stop = true; - } else { - isdir = true; - if (poe->numlower && ovl_is_opaquedir(this)) - stop = upperopaque = true; - } + if (upperdentry && unlikely(ovl_dentry_remote(upperdentry))) { + dput(upperdentry); + err = -EREMOTE; + goto out; } - upperdentry = this; + upperopaque = d.opaque; } - if (!stop && poe->numlower) { + if (!d.stop && poe->numlower) { err = -ENOMEM; - stack = kcalloc(poe->numlower, sizeof(struct path), GFP_KERNEL); + stack = kcalloc(poe->numlower, sizeof(struct path), + GFP_TEMPORARY); if (!stack) goto out_put_upper; } - for (i = 0; !stop && i < poe->numlower; i++) { + for (i = 0; !d.stop && i < poe->numlower; i++) { struct path lowerpath = poe->lowerstack[i]; - this = ovl_lookup_real(lowerpath.dentry, &dentry->d_name); - err = PTR_ERR(this); - if (IS_ERR(this)) + d.last = i == poe->numlower - 1; + err = ovl_lookup_layer(lowerpath.dentry, &d, &this); + if (err) goto out_put; if (!this) continue; - if (ovl_is_whiteout(this)) { - dput(this); - break; - } - /* - * If this is a non-directory then stop here. - */ - if (!d_is_dir(this)) { - if (isdir) { - dput(this); - break; - } - stop = true; - } else { - /* - * Only makes sense to check opaque dir if this is not - * the lowermost layer. - */ - if (i < poe->numlower - 1 && ovl_is_opaquedir(this)) - stop = true; - } stack[ctr].dentry = this; stack[ctr].mnt = lowerpath.mnt; -- cgit v1.2.3 From 02b69b284cd7815239fabfe895bfef9a9eb5a3ce Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:56 +0100 Subject: ovl: lookup redirects If a directory has the "trusted.overlay.redirect" xattr, it means that the value of the xattr should be used to find the underlying directory on the next lower layer. The redirect may be relative or absolute. Absolute redirects begin with a slash. A relative redirect means: instead of the current dentry's name use the value of the redirect to find the directory in the next lower layer. Relative redirects must not contain a slash. An absolute redirect means: look up the directory relative to the root of the overlay using the value of the redirect in the next lower layer. Redirects work on lower layers as well. Signed-off-by: Miklos Szeredi --- fs/overlayfs/namei.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++- fs/overlayfs/overlayfs.h | 1 + fs/overlayfs/ovl_entry.h | 1 + fs/overlayfs/super.c | 1 + 4 files changed, 123 insertions(+), 2 deletions(-) diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index f213297d187e..9ad48d9202a9 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "overlayfs.h" #include "ovl_entry.h" @@ -19,8 +20,66 @@ struct ovl_lookup_data { bool opaque; bool stop; bool last; + char *redirect; }; +static int ovl_check_redirect(struct dentry *dentry, struct ovl_lookup_data *d, + size_t prelen, const char *post) +{ + int res; + char *s, *next, *buf = NULL; + + res = vfs_getxattr(dentry, OVL_XATTR_REDIRECT, NULL, 0); + if (res < 0) { + if (res == -ENODATA || res == -EOPNOTSUPP) + return 0; + goto fail; + } + buf = kzalloc(prelen + res + strlen(post) + 1, GFP_TEMPORARY); + if (!buf) + return -ENOMEM; + + if (res == 0) + goto invalid; + + res = vfs_getxattr(dentry, OVL_XATTR_REDIRECT, buf, res); + if (res < 0) + goto fail; + if (res == 0) + goto invalid; + if (buf[0] == '/') { + for (s = buf; *s++ == '/'; s = next) { + next = strchrnul(s, '/'); + if (s == next) + goto invalid; + } + } else { + if (strchr(buf, '/') != NULL) + goto invalid; + + memmove(buf + prelen, buf, res); + memcpy(buf, d->name.name, prelen); + } + + strcat(buf, post); + kfree(d->redirect); + d->redirect = buf; + d->name.name = d->redirect; + d->name.len = strlen(d->redirect); + + return 0; + +err_free: + kfree(buf); + return 0; +fail: + pr_warn_ratelimited("overlayfs: failed to get redirect (%i)\n", res); + goto err_free; +invalid: + pr_warn_ratelimited("overlayfs: invalid redirect (%s)\n", buf); + goto err_free; +} + static bool ovl_is_opaquedir(struct dentry *dentry) { int res; @@ -38,6 +97,7 @@ static bool ovl_is_opaquedir(struct dentry *dentry) static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d, const char *name, unsigned int namelen, + size_t prelen, const char *post, struct dentry **ret) { struct dentry *this; @@ -74,6 +134,9 @@ static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d, d->stop = d->opaque = true; goto out; } + err = ovl_check_redirect(this, d, prelen, post); + if (err) + goto out_err; out: *ret = this; return 0; @@ -91,7 +154,32 @@ out_err: static int ovl_lookup_layer(struct dentry *base, struct ovl_lookup_data *d, struct dentry **ret) { - return ovl_lookup_single(base, d, d->name.name, d->name.len, ret); + const char *s = d->name.name; + struct dentry *dentry = NULL; + int err; + + if (*s != '/') + return ovl_lookup_single(base, d, d->name.name, d->name.len, + 0, "", ret); + + while (*s++ == '/' && !IS_ERR_OR_NULL(base) && d_can_lookup(base)) { + const char *next = strchrnul(s, '/'); + size_t slen = strlen(s); + + if (WARN_ON(slen > d->name.len) || + WARN_ON(strcmp(d->name.name + d->name.len - slen, s))) + return -EIO; + + err = ovl_lookup_single(base, d, s, next - s, + d->name.len - slen, next, &base); + dput(dentry); + if (err) + return err; + dentry = base; + s = next; + } + *ret = dentry; + return 0; } /* @@ -127,6 +215,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int ctr = 0; struct inode *inode = NULL; bool upperopaque = false; + char *upperredirect = NULL; struct dentry *this; unsigned int i; int err; @@ -136,6 +225,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, .opaque = false, .stop = false, .last = !poe->numlower, + .redirect = NULL, }; if (dentry->d_name.len > ofs->namelen) @@ -153,12 +243,20 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, err = -EREMOTE; goto out; } + + if (d.redirect) { + upperredirect = kstrdup(d.redirect, GFP_KERNEL); + if (!upperredirect) + goto out_put_upper; + if (d.redirect[0] == '/') + poe = dentry->d_sb->s_root->d_fsdata; + } upperopaque = d.opaque; } if (!d.stop && poe->numlower) { err = -ENOMEM; - stack = kcalloc(poe->numlower, sizeof(struct path), + stack = kcalloc(ofs->numlower, sizeof(struct path), GFP_TEMPORARY); if (!stack) goto out_put_upper; @@ -178,6 +276,22 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, stack[ctr].dentry = this; stack[ctr].mnt = lowerpath.mnt; ctr++; + + if (d.stop) + break; + + if (d.redirect && + d.redirect[0] == '/' && + poe != dentry->d_sb->s_root->d_fsdata) { + poe = dentry->d_sb->s_root->d_fsdata; + + /* Find the current layer on the root dentry */ + for (i = 0; i < poe->numlower; i++) + if (poe->lowerstack[i].mnt == lowerpath.mnt) + break; + if (WARN_ON(i == poe->numlower)) + break; + } } oe = ovl_alloc_entry(ctr); @@ -208,9 +322,11 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, revert_creds(old_cred); oe->opaque = upperopaque; + oe->redirect = upperredirect; oe->__upperdentry = upperdentry; memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); kfree(stack); + kfree(d.redirect); dentry->d_fsdata = oe; d_add(dentry, inode); @@ -224,7 +340,9 @@ out_put: kfree(stack); out_put_upper: dput(upperdentry); + kfree(upperredirect); out: + kfree(d.redirect); revert_creds(old_cred); return ERR_PTR(err); } diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index f6e4d3539a25..e76d9d529e64 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -19,6 +19,7 @@ enum ovl_path_type { #define OVL_XATTR_PREFIX XATTR_TRUSTED_PREFIX "overlay." #define OVL_XATTR_OPAQUE OVL_XATTR_PREFIX "opaque" +#define OVL_XATTR_REDIRECT OVL_XATTR_PREFIX "redirect" #define OVL_ISUPPER_MASK 1UL diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index b10745edfc93..eb29882b6a54 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -35,6 +35,7 @@ struct ovl_entry { union { struct { u64 version; + const char *redirect; bool opaque; }; struct rcu_head rcu; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index aadb25413e6e..4e44e865b716 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -37,6 +37,7 @@ static void ovl_dentry_release(struct dentry *dentry) unsigned int i; dput(oe->__upperdentry); + kfree(oe->redirect); for (i = 0; i < oe->numlower; i++) dput(oe->lowerstack[i].dentry); kfree_rcu(oe, rcu); -- cgit v1.2.3 From a6c6065511411c57167a6cdae0c33263fb662b51 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:56 +0100 Subject: ovl: redirect on rename-dir Current code returns EXDEV when a directory would need to be copied up to move. We could copy up the directory tree in this case, but there's another, simpler solution: point to old lower directory from moved upper directory. This is achieved with a "trusted.overlay.redirect" xattr storing the path relative to the root of the overlay. After such attribute has been set, the directory can be moved without further actions required. This is a backward incompatible feature, old kernels won't be able to correctly mount an overlay containing redirected directories. Signed-off-by: Miklos Szeredi --- Documentation/filesystems/overlayfs.txt | 21 ++++- fs/overlayfs/copy_up.c | 20 ++--- fs/overlayfs/dir.c | 138 ++++++++++++++++++++++++++++---- fs/overlayfs/overlayfs.h | 4 + fs/overlayfs/ovl_entry.h | 1 + fs/overlayfs/super.c | 12 +++ fs/overlayfs/util.c | 29 +++++++ 7 files changed, 195 insertions(+), 30 deletions(-) diff --git a/Documentation/filesystems/overlayfs.txt b/Documentation/filesystems/overlayfs.txt index 7aeb8e8d80cf..fb6f3070c6fe 100644 --- a/Documentation/filesystems/overlayfs.txt +++ b/Documentation/filesystems/overlayfs.txt @@ -130,6 +130,23 @@ directory. Readdir on directories that are not merged is simply handled by the underlying directory (upper or lower). +renaming directories +-------------------- + +When renaming a directory that is on the lower layer or merged (i.e. the +directory was not created on the upper layer to start with) overlayfs can +handle it in two different ways: + +1) return EXDEV error: this error is returned by rename(2) when trying to + move a file or directory across filesystem boundaries. Hence + applications are usually prepared to hande this error (mv(1) for example + recursively copies the directory tree). This is the default behavior. + +2) If the "redirect_dir" feature is enabled, then the directory will be + copied up (but not the contents). Then the "trusted.overlay.redirect" + extended attribute is set to the path of the original location from the + root of the overlay. Finally the directory is moved to the new + location. Non-directories --------------- @@ -189,8 +206,8 @@ If a file with multiple hard links is copied up, then this will "break" the link. Changes will not be propagated to other names referring to the same inode. -Directory trees are not copied up. If rename(2) is performed on a directory -which is on the lower layer or is merged, then -EXDEV will be returned. +Unless "redirect_dir" feature is enabled, rename(2) on a lower or merged +directory will fail with EXDEV. Changes to underlying filesystems --------------------------------- diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index f18c1a616e9e..e191c631b17f 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -324,17 +324,11 @@ out_cleanup: /* * Copy up a single dentry * - * Directory renames only allowed on "pure upper" (already created on - * upper filesystem, never copied up). Directories which are on lower or - * are merged may not be renamed. For these -EXDEV is returned and - * userspace has to deal with it. This means, when copying up a - * directory we can rely on it and ancestors being stable. - * - * Non-directory renames start with copy up of source if necessary. The - * actual rename will only proceed once the copy up was successful. Copy - * up uses upper parent i_mutex for exclusion. Since rename can change - * d_parent it is possible that the copy up will lock the old parent. At - * that point the file will have already been copied up anyway. + * All renames start with copy up of source if necessary. The actual + * rename will only proceed once the copy up was successful. Copy up uses + * upper parent i_mutex for exclusion. Since rename can change d_parent it + * is possible that the copy up will lock the old parent. At that point + * the file will have already been copied up anyway. */ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, struct path *lowerpath, struct kstat *stat) @@ -346,7 +340,6 @@ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, struct path parentpath; struct dentry *lowerdentry = lowerpath->dentry; struct dentry *upperdir; - struct dentry *upperdentry; const char *link = NULL; if (WARN_ON(!workdir)) @@ -372,8 +365,7 @@ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, pr_err("overlayfs: failed to lock workdir+upperdir\n"); goto out_unlock; } - upperdentry = ovl_dentry_upper(dentry); - if (upperdentry) { + if (ovl_dentry_upper(dentry)) { /* Raced with another copy-up? Nothing to do, then... */ err = 0; goto out_unlock; diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index f24b6b967901..c1de84c1c5ec 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "overlayfs.h" void ovl_cleanup(struct inode *wdir, struct dentry *wdentry) @@ -757,6 +758,104 @@ static bool ovl_type_merge_or_lower(struct dentry *dentry) return OVL_TYPE_MERGE(type) || !OVL_TYPE_UPPER(type); } +static bool ovl_can_move(struct dentry *dentry) +{ + return ovl_redirect_dir(dentry->d_sb) || + !d_is_dir(dentry) || !ovl_type_merge_or_lower(dentry); +} + +#define OVL_REDIRECT_MAX 256 + +static char *ovl_get_redirect(struct dentry *dentry, bool samedir) +{ + char *buf, *ret; + struct dentry *d, *tmp; + int buflen = OVL_REDIRECT_MAX + 1; + + if (samedir) { + ret = kstrndup(dentry->d_name.name, dentry->d_name.len, + GFP_KERNEL); + goto out; + } + + buf = ret = kmalloc(buflen, GFP_TEMPORARY); + if (!buf) + goto out; + + buflen--; + buf[buflen] = '\0'; + for (d = dget(dentry); !IS_ROOT(d);) { + const char *name; + int thislen; + + spin_lock(&d->d_lock); + name = ovl_dentry_get_redirect(d); + if (name) { + thislen = strlen(name); + } else { + name = d->d_name.name; + thislen = d->d_name.len; + } + + /* If path is too long, fall back to userspace move */ + if (thislen + (name[0] != '/') > buflen) { + ret = ERR_PTR(-EXDEV); + spin_unlock(&d->d_lock); + goto out_put; + } + + buflen -= thislen; + memcpy(&buf[buflen], name, thislen); + tmp = dget_dlock(d->d_parent); + spin_unlock(&d->d_lock); + + dput(d); + d = tmp; + + /* Absolute redirect: finished */ + if (buf[buflen] == '/') + break; + buflen--; + buf[buflen] = '/'; + } + ret = kstrdup(&buf[buflen], GFP_KERNEL); +out_put: + dput(d); + kfree(buf); +out: + return ret ? ret : ERR_PTR(-ENOMEM); +} + +static int ovl_set_redirect(struct dentry *dentry, bool samedir) +{ + int err; + const char *redirect = ovl_dentry_get_redirect(dentry); + + if (redirect && (samedir || redirect[0] == '/')) + return 0; + + redirect = ovl_get_redirect(dentry, samedir); + if (IS_ERR(redirect)) + return PTR_ERR(redirect); + + err = ovl_do_setxattr(ovl_dentry_upper(dentry), OVL_XATTR_REDIRECT, + redirect, strlen(redirect), 0); + if (!err) { + spin_lock(&dentry->d_lock); + ovl_dentry_set_redirect(dentry, redirect); + spin_unlock(&dentry->d_lock); + } else { + kfree(redirect); + if (err == -EOPNOTSUPP) + ovl_clear_redirect_dir(dentry->d_sb); + else + pr_warn_ratelimited("overlay: failed to set redirect (%i)\n", err); + /* Fall back to userspace copy-up */ + err = -EXDEV; + } + return err; +} + static int ovl_rename(struct inode *olddir, struct dentry *old, struct inode *newdir, struct dentry *new, unsigned int flags) @@ -773,6 +872,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, bool overwrite = !(flags & RENAME_EXCHANGE); bool is_dir = d_is_dir(old); bool new_is_dir = d_is_dir(new); + bool samedir = olddir == newdir; struct dentry *opaquedir = NULL; const struct cred *old_cred = NULL; @@ -784,9 +884,9 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, /* Don't copy up directory trees */ err = -EXDEV; - if (is_dir && ovl_type_merge_or_lower(old)) + if (!ovl_can_move(old)) goto out; - if (!overwrite && new_is_dir && ovl_type_merge_or_lower(new)) + if (!overwrite && !ovl_can_move(new)) goto out; err = ovl_want_write(old); @@ -837,7 +937,6 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, trap = lock_rename(new_upperdir, old_upperdir); - olddentry = lookup_one_len(old->d_name.name, old_upperdir, old->d_name.len); err = PTR_ERR(olddentry); @@ -880,18 +979,29 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, if (WARN_ON(olddentry->d_inode == newdentry->d_inode)) goto out_dput; - if (is_dir && !old_opaque && ovl_lower_positive(new)) { - err = ovl_set_opaque(olddentry); - if (err) - goto out_dput; - ovl_dentry_set_opaque(old, true); + if (is_dir) { + if (ovl_type_merge_or_lower(old)) { + err = ovl_set_redirect(old, samedir); + if (err) + goto out_dput; + } else if (!old_opaque && ovl_lower_positive(new)) { + err = ovl_set_opaque(olddentry); + if (err) + goto out_dput; + ovl_dentry_set_opaque(old, true); + } } - if (!overwrite && - new_is_dir && !new_opaque && ovl_lower_positive(old)) { - err = ovl_set_opaque(newdentry); - if (err) - goto out_dput; - ovl_dentry_set_opaque(new, true); + if (!overwrite && new_is_dir) { + if (ovl_type_merge_or_lower(new)) { + err = ovl_set_redirect(new, samedir); + if (err) + goto out_dput; + } else if (!new_opaque && ovl_lower_positive(old)) { + err = ovl_set_opaque(newdentry); + if (err) + goto out_dput; + ovl_dentry_set_opaque(new, true); + } } err = ovl_do_rename(old_upperdir->d_inode, olddentry, diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index e76d9d529e64..bdda37fa3f67 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -157,6 +157,10 @@ void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache); bool ovl_dentry_is_opaque(struct dentry *dentry); bool ovl_dentry_is_whiteout(struct dentry *dentry); void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque); +bool ovl_redirect_dir(struct super_block *sb); +void ovl_clear_redirect_dir(struct super_block *sb); +const char *ovl_dentry_get_redirect(struct dentry *dentry); +void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect); void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry); void ovl_inode_init(struct inode *inode, struct inode *realinode, bool is_upper); diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index eb29882b6a54..d14bca1850d9 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -13,6 +13,7 @@ struct ovl_config { char *upperdir; char *workdir; bool default_permissions; + bool redirect_dir; }; /* private information held for overlayfs's superblock */ diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 4e44e865b716..520f9ab0e9ef 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -226,6 +226,8 @@ enum { OPT_UPPERDIR, OPT_WORKDIR, OPT_DEFAULT_PERMISSIONS, + OPT_REDIRECT_DIR_ON, + OPT_REDIRECT_DIR_OFF, OPT_ERR, }; @@ -234,6 +236,8 @@ static const match_table_t ovl_tokens = { {OPT_UPPERDIR, "upperdir=%s"}, {OPT_WORKDIR, "workdir=%s"}, {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, + {OPT_REDIRECT_DIR_ON, "redirect_dir=on"}, + {OPT_REDIRECT_DIR_OFF, "redirect_dir=off"}, {OPT_ERR, NULL} }; @@ -298,6 +302,14 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) config->default_permissions = true; break; + case OPT_REDIRECT_DIR_ON: + config->redirect_dir = true; + break; + + case OPT_REDIRECT_DIR_OFF: + config->redirect_dir = false; + break; + default: pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p); return -EINVAL; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 0d45a84468d2..260b215852a3 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -176,6 +176,35 @@ void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque) oe->opaque = opaque; } +bool ovl_redirect_dir(struct super_block *sb) +{ + struct ovl_fs *ofs = sb->s_fs_info; + + return ofs->config.redirect_dir; +} + +void ovl_clear_redirect_dir(struct super_block *sb) +{ + struct ovl_fs *ofs = sb->s_fs_info; + + ofs->config.redirect_dir = false; +} + +const char *ovl_dentry_get_redirect(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + return oe->redirect; +} + +void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + kfree(oe->redirect); + oe->redirect = redirect; +} + void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry) { struct ovl_entry *oe = dentry->d_fsdata; -- cgit v1.2.3 From d15951198eaccb92c6b49e62cb72f5ff62da2236 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 26 Oct 2016 12:34:06 +0300 Subject: ovl: check for emptiness of redirect dir Before introducing redirect_dir feature, the condition !ovl_lower_positive(dentry) for a directory, implied that it is a pure upper directory, which may be removed if empty. Now that directory can be redirect, it is possible that upper does not cover any lower (i.e. !ovl_lower_positive(dentry)), but the directory is a merge (with redirected path) and maybe non empty. Check for this case in ovl_remove_upper(). This change fixes the following test case from rename-pop-dir.py of unionmount-testsuite: """Remove dir and rename old name""" d = ctx.non_empty_dir() d2 = ctx.no_dir() ctx.rmdir(d, err=ENOTEMPTY) ctx.rename(d, d2) ctx.rmdir(d, err=ENOENT) ctx.rmdir(d2, err=ENOTEMPTY) ./run --ov rename-pop-dir /mnt/a/no_dir103: Expected error (Directory not empty) was not produced Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index c1de84c1c5ec..4257a4a0ed72 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -674,8 +674,17 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir) struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent); struct inode *dir = upperdir->d_inode; struct dentry *upper; + struct dentry *opaquedir = NULL; int err; + /* Redirect dir can be !ovl_lower_positive && OVL_TYPE_MERGE */ + if (is_dir && ovl_dentry_get_redirect(dentry)) { + opaquedir = ovl_check_empty_and_clear(dentry); + err = PTR_ERR(opaquedir); + if (IS_ERR(opaquedir)) + goto out; + } + inode_lock_nested(dir, I_MUTEX_PARENT); upper = lookup_one_len(dentry->d_name.name, upperdir, dentry->d_name.len); @@ -684,14 +693,15 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir) goto out_unlock; err = -ESTALE; - if (upper == ovl_dentry_upper(dentry)) { - if (is_dir) - err = vfs_rmdir(dir, upper); - else - err = vfs_unlink(dir, upper, NULL); - ovl_dentry_version_inc(dentry->d_parent); - } - dput(upper); + if ((opaquedir && upper != opaquedir) || + (!opaquedir && upper != ovl_dentry_upper(dentry))) + goto out_dput_upper; + + if (is_dir) + err = vfs_rmdir(dir, upper); + else + err = vfs_unlink(dir, upper, NULL); + ovl_dentry_version_inc(dentry->d_parent); /* * Keeping this dentry hashed would mean having to release @@ -701,9 +711,12 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir) */ if (!err) d_drop(dentry); +out_dput_upper: + dput(upper); out_unlock: inode_unlock(dir); - + dput(opaquedir); +out: return err; } -- cgit v1.2.3 From 688ea0e5a0e2278e2fcd0014324ab1ba68e70ad7 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:57 +0100 Subject: ovl: allow redirect_dir to default to "on" This patch introduces a kernel config option and a module param. Both can be used independently to turn the default value of redirect_dir on or off. Signed-off-by: Miklos Szeredi --- fs/overlayfs/Kconfig | 14 ++++++++++++++ fs/overlayfs/super.c | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/fs/overlayfs/Kconfig b/fs/overlayfs/Kconfig index 34355818a2e0..0daac5112f7a 100644 --- a/fs/overlayfs/Kconfig +++ b/fs/overlayfs/Kconfig @@ -8,3 +8,17 @@ config OVERLAY_FS merged with the 'upper' object. For more information see Documentation/filesystems/overlayfs.txt + +config OVERLAY_FS_REDIRECT_DIR + bool "Overlayfs: turn on redirect dir feature by default" + depends on OVERLAY_FS + help + If this config option is enabled then overlay filesystems will use + redirects when renaming directories by default. In this case it is + still possible to turn off redirects globally with the + "redirect_dir=off" module option or on a filesystem instance basis + with the "redirect_dir=off" mount option. + + Note, that redirects are not backward compatible. That is, mounting + an overlay which has redirects on a kernel that doesn't support this + feature will have unexpected results. diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 520f9ab0e9ef..2750aef24d16 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -28,6 +28,10 @@ struct ovl_dir_cache; #define OVL_MAX_STACK 500 +static bool ovl_redirect_dir_def = IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_DIR); +module_param_named(redirect_dir, ovl_redirect_dir_def, bool, 0644); +MODULE_PARM_DESC(ovl_redirect_dir_def, + "Default to on or off for the redirect_dir feature"); static void ovl_dentry_release(struct dentry *dentry) { @@ -702,6 +706,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (!ufs) goto out; + ufs->config.redirect_dir = ovl_redirect_dir_def; err = ovl_parse_opt((char *) data, &ufs->config); if (err) goto out_free_config; -- cgit v1.2.3 From 3ea22a71b65b6743a53e286ff4991a06b9d2597c Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:57 +0100 Subject: ovl: allow setting max size of redirect Add a module option to allow tuning the max size of absolute redirects. Default is 256. Size of relative redirects is naturally limited by the the underlying filesystem's max filename length (usually 255). Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 4257a4a0ed72..b84d61b353cd 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -12,12 +12,18 @@ #include #include #include +#include #include #include #include #include #include "overlayfs.h" +static unsigned short ovl_redirect_max = 256; +module_param_named(redirect_max, ovl_redirect_max, ushort, 0644); +MODULE_PARM_DESC(ovl_redirect_max, + "Maximum length of absolute redirect xattr value"); + void ovl_cleanup(struct inode *wdir, struct dentry *wdentry) { int err; @@ -777,13 +783,11 @@ static bool ovl_can_move(struct dentry *dentry) !d_is_dir(dentry) || !ovl_type_merge_or_lower(dentry); } -#define OVL_REDIRECT_MAX 256 - static char *ovl_get_redirect(struct dentry *dentry, bool samedir) { char *buf, *ret; struct dentry *d, *tmp; - int buflen = OVL_REDIRECT_MAX + 1; + int buflen = ovl_redirect_max + 1; if (samedir) { ret = kstrndup(dentry->d_name.name, dentry->d_name.len, -- cgit v1.2.3 From c5bef3a72b9d8a2040d5e9f4bde03db7c86bbfce Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Tue, 22 Nov 2016 11:47:09 +0200 Subject: ovl: show redirect_dir mount option Show the value of redirect_dir in /proc/mounts. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 2750aef24d16..4bd1e9c7246f 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -204,6 +204,9 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry) } if (ufs->config.default_permissions) seq_puts(m, ",default_permissions"); + if (ufs->config.redirect_dir != ovl_redirect_dir_def) + seq_printf(m, ",redirect_dir=%s", + ufs->config.redirect_dir ? "on" : "off"); return 0; } -- cgit v1.2.3 From 5cf5b477f0ca33f56a30c7ec00e61a6204da2efb Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 16 Dec 2016 11:02:57 +0100 Subject: ovl: opaque cleanup oe->opaque is set for a) whiteouts b) directories having the "trusted.overlay.opaque" xattr Case b can be simplified, since setting the xattr always implies setting oe->opaque. Also once set, the opaque flag is never cleared. Don't need to set opaque flag for non-directories. Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 6 ------ fs/overlayfs/dir.c | 43 +++++++++++++++++++++---------------------- fs/overlayfs/overlayfs.h | 2 +- fs/overlayfs/util.c | 5 +++-- 4 files changed, 25 insertions(+), 31 deletions(-) diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index e191c631b17f..6c3aaf45e9cf 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -303,12 +303,6 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, ovl_dentry_update(dentry, newdentry); ovl_inode_update(d_inode(dentry), d_inode(newdentry)); newdentry = NULL; - - /* - * Non-directores become opaque when copied up. - */ - if (!S_ISDIR(stat->mode)) - ovl_dentry_set_opaque(dentry, true); out2: dput(upper); out1: diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index b84d61b353cd..76e39aaaa038 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -128,9 +128,15 @@ int ovl_create_real(struct inode *dir, struct dentry *newdentry, return err; } -static int ovl_set_opaque(struct dentry *upperdentry) +static int ovl_set_opaque(struct dentry *dentry, struct dentry *upperdentry) { - return ovl_do_setxattr(upperdentry, OVL_XATTR_OPAQUE, "y", 1, 0); + int err; + + err = ovl_do_setxattr(upperdentry, OVL_XATTR_OPAQUE, "y", 1, 0); + if (!err) + ovl_dentry_set_opaque(dentry); + + return err; } static int ovl_dir_getattr(struct vfsmount *mnt, struct dentry *dentry, @@ -274,7 +280,7 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry, if (err) goto out_cleanup; - err = ovl_set_opaque(opaquedir); + err = ovl_set_opaque(dentry, opaquedir); if (err) goto out_cleanup; @@ -435,7 +441,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, } if (!hardlink && S_ISDIR(stat->mode)) { - err = ovl_set_opaque(newdentry); + err = ovl_set_opaque(dentry, newdentry); if (err) goto out_cleanup; @@ -996,29 +1002,22 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, if (WARN_ON(olddentry->d_inode == newdentry->d_inode)) goto out_dput; + err = 0; if (is_dir) { - if (ovl_type_merge_or_lower(old)) { + if (ovl_type_merge_or_lower(old)) err = ovl_set_redirect(old, samedir); - if (err) - goto out_dput; - } else if (!old_opaque && ovl_lower_positive(new)) { - err = ovl_set_opaque(olddentry); - if (err) - goto out_dput; - ovl_dentry_set_opaque(old, true); - } + else if (!old_opaque && ovl_lower_positive(new)) + err = ovl_set_opaque(old, olddentry); + if (err) + goto out_dput; } if (!overwrite && new_is_dir) { - if (ovl_type_merge_or_lower(new)) { + if (ovl_type_merge_or_lower(new)) err = ovl_set_redirect(new, samedir); - if (err) - goto out_dput; - } else if (!new_opaque && ovl_lower_positive(old)) { - err = ovl_set_opaque(newdentry); - if (err) - goto out_dput; - ovl_dentry_set_opaque(new, true); - } + else if (!new_opaque && ovl_lower_positive(old)) + err = ovl_set_opaque(new, newdentry); + if (err) + goto out_dput; } err = ovl_do_rename(old_upperdir->d_inode, olddentry, diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index bdda37fa3f67..a83de5d5b8a0 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -156,7 +156,7 @@ struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry); void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache); bool ovl_dentry_is_opaque(struct dentry *dentry); bool ovl_dentry_is_whiteout(struct dentry *dentry); -void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque); +void ovl_dentry_set_opaque(struct dentry *dentry); bool ovl_redirect_dir(struct super_block *sb); void ovl_clear_redirect_dir(struct super_block *sb); const char *ovl_dentry_get_redirect(struct dentry *dentry); diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 260b215852a3..952286f4826c 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -170,10 +170,11 @@ bool ovl_dentry_is_whiteout(struct dentry *dentry) return !dentry->d_inode && ovl_dentry_is_opaque(dentry); } -void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque) +void ovl_dentry_set_opaque(struct dentry *dentry) { struct ovl_entry *oe = dentry->d_fsdata; - oe->opaque = opaque; + + oe->opaque = true; } bool ovl_redirect_dir(struct super_block *sb) -- cgit v1.2.3 From 97c684cc911060ba7f97c0925eaf842f159a39e8 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Mon, 21 Nov 2016 18:57:34 +0200 Subject: ovl: create directories inside merged parent opaque The benefit of making directories opaque on creation is that lookups can stop short when they reach the original created directory, instead of continue lookup the entire depth of parent directory stack. The best case is overlay with N layers, performing lookup for first level directory, which exists only in upper. In that case, there will be only one lookup instead of N. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 76e39aaaa038..3cc65e60df02 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -184,6 +184,11 @@ static void ovl_instantiate(struct dentry *dentry, struct inode *inode, d_instantiate(dentry, inode); } +static bool ovl_type_merge(struct dentry *dentry) +{ + return OVL_TYPE_MERGE(ovl_path_type(dentry)); +} + static int ovl_create_upper(struct dentry *dentry, struct inode *inode, struct kstat *stat, const char *link, struct dentry *hardlink) @@ -206,6 +211,11 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode, if (err) goto out_dput; + if (ovl_type_merge(dentry->d_parent)) { + /* Setting opaque here is just an optimization, allow to fail */ + ovl_set_opaque(dentry, newdentry); + } + ovl_instantiate(dentry, inode, newdentry, !!hardlink); newdentry = NULL; out_dput: @@ -1006,7 +1016,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, if (is_dir) { if (ovl_type_merge_or_lower(old)) err = ovl_set_redirect(old, samedir); - else if (!old_opaque && ovl_lower_positive(new)) + else if (!old_opaque && ovl_type_merge(new->d_parent)) err = ovl_set_opaque(old, olddentry); if (err) goto out_dput; @@ -1014,7 +1024,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, if (!overwrite && new_is_dir) { if (ovl_type_merge_or_lower(new)) err = ovl_set_redirect(new, samedir); - else if (!new_opaque && ovl_lower_positive(old)) + else if (!new_opaque && ovl_type_merge(old->d_parent)) err = ovl_set_opaque(new, newdentry); if (err) goto out_dput; -- cgit v1.2.3 From 9aba652190f8cdced66967c97d6159de0cc8478e Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Sat, 12 Nov 2016 21:36:03 +0200 Subject: ovl: fold ovl_copy_up_truncate() into ovl_copy_up() This removes code duplication. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 14 +++++++++++--- fs/overlayfs/inode.c | 33 +-------------------------------- fs/overlayfs/overlayfs.h | 3 +-- 3 files changed, 13 insertions(+), 37 deletions(-) diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 6c3aaf45e9cf..e8cacf7a8dcc 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -324,8 +324,8 @@ out_cleanup: * is possible that the copy up will lock the old parent. At that point * the file will have already been copied up anyway. */ -int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, - struct path *lowerpath, struct kstat *stat) +static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, + struct path *lowerpath, struct kstat *stat) { DEFINE_DELAYED_CALL(done); struct dentry *workdir = ovl_workdir(dentry); @@ -378,7 +378,7 @@ out_unlock: return err; } -int ovl_copy_up(struct dentry *dentry) +int ovl_copy_up_flags(struct dentry *dentry, int flags) { int err = 0; const struct cred *old_cred = ovl_override_creds(dentry->d_sb); @@ -408,6 +408,9 @@ int ovl_copy_up(struct dentry *dentry) ovl_path_lower(next, &lowerpath); err = vfs_getattr(&lowerpath, &stat); + /* maybe truncate regular file. this has no effect on dirs */ + if (flags & O_TRUNC) + stat.size = 0; if (!err) err = ovl_copy_up_one(parent, next, &lowerpath, &stat); @@ -418,3 +421,8 @@ int ovl_copy_up(struct dentry *dentry) return err; } + +int ovl_copy_up(struct dentry *dentry) +{ + return ovl_copy_up_flags(dentry, 0); +} diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index a10e948d24fa..1ab8b0dbc237 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -13,34 +13,6 @@ #include #include "overlayfs.h" -static int ovl_copy_up_truncate(struct dentry *dentry) -{ - int err; - struct dentry *parent; - struct kstat stat; - struct path lowerpath; - const struct cred *old_cred; - - parent = dget_parent(dentry); - err = ovl_copy_up(parent); - if (err) - goto out_dput_parent; - - ovl_path_lower(dentry, &lowerpath); - - old_cred = ovl_override_creds(dentry->d_sb); - err = vfs_getattr(&lowerpath, &stat); - if (!err) { - stat.size = 0; - err = ovl_copy_up_one(parent, dentry, &lowerpath, &stat); - } - revert_creds(old_cred); - -out_dput_parent: - dput(parent); - return err; -} - int ovl_setattr(struct dentry *dentry, struct iattr *attr) { int err; @@ -281,10 +253,7 @@ int ovl_open_maybe_copy_up(struct dentry *dentry, unsigned int file_flags) if (ovl_open_need_copy_up(file_flags, type, realpath.dentry)) { err = ovl_want_write(dentry); if (!err) { - if (file_flags & O_TRUNC) - err = ovl_copy_up_truncate(dentry); - else - err = ovl_copy_up(dentry); + err = ovl_copy_up_flags(dentry, file_flags); ovl_drop_write(dentry); } } diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index a83de5d5b8a0..e07aa7b0ddb7 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -219,7 +219,6 @@ void ovl_cleanup(struct inode *dir, struct dentry *dentry); /* copy_up.c */ int ovl_copy_up(struct dentry *dentry); -int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, - struct path *lowerpath, struct kstat *stat); +int ovl_copy_up_flags(struct dentry *dentry, int flags); int ovl_copy_xattr(struct dentry *old, struct dentry *new); int ovl_set_attr(struct dentry *upper, struct kstat *stat); -- cgit v1.2.3 From 32a3d848eb91a298334991f1891e12e0362f91db Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 4 Dec 2016 17:33:17 +0000 Subject: ovl: clean up kstat usage FWIW, there's a bit of abuse of struct kstat in overlayfs object creation paths - for one thing, it ends up with a very small subset of struct kstat (mode + rdev), for another it also needs link in case of symlinks and ends up passing it separately. IMO it would be better to introduce a separate object for that. In principle, we might even lift that thing into general API and switch ->mkdir()/->mknod()/->symlink() to identical calling conventions. Hell knows, perhaps ->create() as well... Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 12 ++++++----- fs/overlayfs/dir.c | 56 ++++++++++++++++++++++++------------------------ fs/overlayfs/overlayfs.h | 7 +++++- fs/overlayfs/super.c | 9 ++++---- 4 files changed, 45 insertions(+), 39 deletions(-) diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index e8cacf7a8dcc..0e9940f9f34a 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -238,10 +238,15 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, struct inode *udir = upperdir->d_inode; struct dentry *newdentry = NULL; struct dentry *upper = NULL; - umode_t mode = stat->mode; int err; const struct cred *old_creds = NULL; struct cred *new_creds = NULL; + struct cattr cattr = { + /* Can't properly set mode on creation because of the umask */ + .mode = stat->mode & S_IFMT, + .rdev = stat->rdev, + .link = link + }; newdentry = ovl_lookup_temp(workdir, dentry); err = PTR_ERR(newdentry); @@ -261,10 +266,7 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, if (new_creds) old_creds = override_creds(new_creds); - /* Can't properly set mode on creation because of the umask */ - stat->mode &= S_IFMT; - err = ovl_create_real(wdir, newdentry, stat, link, NULL, true); - stat->mode = mode; + err = ovl_create_real(wdir, newdentry, &cattr, NULL, true); if (new_creds) { revert_creds(old_creds); diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 3cc65e60df02..16e06dd89457 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -82,8 +82,7 @@ static struct dentry *ovl_whiteout(struct dentry *workdir, } int ovl_create_real(struct inode *dir, struct dentry *newdentry, - struct kstat *stat, const char *link, - struct dentry *hardlink, bool debug) + struct cattr *attr, struct dentry *hardlink, bool debug) { int err; @@ -93,13 +92,13 @@ int ovl_create_real(struct inode *dir, struct dentry *newdentry, if (hardlink) { err = ovl_do_link(hardlink, dir, newdentry, debug); } else { - switch (stat->mode & S_IFMT) { + switch (attr->mode & S_IFMT) { case S_IFREG: - err = ovl_do_create(dir, newdentry, stat->mode, debug); + err = ovl_do_create(dir, newdentry, attr->mode, debug); break; case S_IFDIR: - err = ovl_do_mkdir(dir, newdentry, stat->mode, debug); + err = ovl_do_mkdir(dir, newdentry, attr->mode, debug); break; case S_IFCHR: @@ -107,11 +106,11 @@ int ovl_create_real(struct inode *dir, struct dentry *newdentry, case S_IFIFO: case S_IFSOCK: err = ovl_do_mknod(dir, newdentry, - stat->mode, stat->rdev, debug); + attr->mode, attr->rdev, debug); break; case S_IFLNK: - err = ovl_do_symlink(dir, newdentry, link, debug); + err = ovl_do_symlink(dir, newdentry, attr->link, debug); break; default: @@ -190,8 +189,7 @@ static bool ovl_type_merge(struct dentry *dentry) } static int ovl_create_upper(struct dentry *dentry, struct inode *inode, - struct kstat *stat, const char *link, - struct dentry *hardlink) + struct cattr *attr, struct dentry *hardlink) { struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent); struct inode *udir = upperdir->d_inode; @@ -199,7 +197,7 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode, int err; if (!hardlink && !IS_POSIXACL(udir)) - stat->mode &= ~current_umask(); + attr->mode &= ~current_umask(); inode_lock_nested(udir, I_MUTEX_PARENT); newdentry = lookup_one_len(dentry->d_name.name, upperdir, @@ -207,7 +205,7 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode, err = PTR_ERR(newdentry); if (IS_ERR(newdentry)) goto out_unlock; - err = ovl_create_real(udir, newdentry, stat, link, hardlink, false); + err = ovl_create_real(udir, newdentry, attr, hardlink, false); if (err) goto out_dput; @@ -282,7 +280,8 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry, if (IS_ERR(opaquedir)) goto out_unlock; - err = ovl_create_real(wdir, opaquedir, &stat, NULL, NULL, true); + err = ovl_create_real(wdir, opaquedir, + &(struct cattr){.mode = stat.mode}, NULL, true); if (err) goto out_dput; @@ -382,7 +381,7 @@ out_free: } static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, - struct kstat *stat, const char *link, + struct cattr *cattr, struct dentry *hardlink) { struct dentry *workdir = ovl_workdir(dentry); @@ -399,7 +398,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, if (!hardlink) { err = posix_acl_create(dentry->d_parent->d_inode, - &stat->mode, &default_acl, &acl); + &cattr->mode, &default_acl, &acl); if (err) return err; } @@ -419,7 +418,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, if (IS_ERR(upper)) goto out_dput; - err = ovl_create_real(wdir, newdentry, stat, link, hardlink, true); + err = ovl_create_real(wdir, newdentry, cattr, hardlink, true); if (err) goto out_dput2; @@ -427,10 +426,11 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, * mode could have been mutilated due to umask (e.g. sgid directory) */ if (!hardlink && - !S_ISLNK(stat->mode) && newdentry->d_inode->i_mode != stat->mode) { + !S_ISLNK(cattr->mode) && + newdentry->d_inode->i_mode != cattr->mode) { struct iattr attr = { .ia_valid = ATTR_MODE, - .ia_mode = stat->mode, + .ia_mode = cattr->mode, }; inode_lock(newdentry->d_inode); err = notify_change(newdentry, &attr, NULL); @@ -450,7 +450,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, goto out_cleanup; } - if (!hardlink && S_ISDIR(stat->mode)) { + if (!hardlink && S_ISDIR(cattr->mode)) { err = ovl_set_opaque(dentry, newdentry); if (err) goto out_cleanup; @@ -487,8 +487,7 @@ out_cleanup: } static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, - struct kstat *stat, const char *link, - struct dentry *hardlink) + struct cattr *attr, struct dentry *hardlink) { int err; const struct cred *old_cred; @@ -506,7 +505,7 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, override_cred->fsgid = inode->i_gid; if (!hardlink) { err = security_dentry_create_files_as(dentry, - stat->mode, &dentry->d_name, old_cred, + attr->mode, &dentry->d_name, old_cred, override_cred); if (err) { put_cred(override_cred); @@ -517,11 +516,11 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, put_cred(override_cred); if (!ovl_dentry_is_whiteout(dentry)) - err = ovl_create_upper(dentry, inode, stat, link, + err = ovl_create_upper(dentry, inode, attr, hardlink); else - err = ovl_create_over_whiteout(dentry, inode, stat, - link, hardlink); + err = ovl_create_over_whiteout(dentry, inode, attr, + hardlink); } out_revert_creds: revert_creds(old_cred); @@ -540,8 +539,9 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, { int err; struct inode *inode; - struct kstat stat = { + struct cattr attr = { .rdev = rdev, + .link = link, }; err = ovl_want_write(dentry); @@ -554,9 +554,9 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, goto out_drop_write; inode_init_owner(inode, dentry->d_parent->d_inode, mode); - stat.mode = inode->i_mode; + attr.mode = inode->i_mode; - err = ovl_create_or_link(dentry, inode, &stat, link, NULL); + err = ovl_create_or_link(dentry, inode, &attr, NULL); if (err) iput(inode); @@ -610,7 +610,7 @@ static int ovl_link(struct dentry *old, struct inode *newdir, inode = d_inode(old); ihold(inode); - err = ovl_create_or_link(new, inode, NULL, NULL, ovl_dentry_upper(old)); + err = ovl_create_or_link(new, inode, NULL, ovl_dentry_upper(old)); if (err) iput(inode); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index e07aa7b0ddb7..8af450b0e57a 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -212,8 +212,13 @@ static inline void ovl_copyattr(struct inode *from, struct inode *to) /* dir.c */ extern const struct inode_operations ovl_dir_inode_operations; struct dentry *ovl_lookup_temp(struct dentry *workdir, struct dentry *dentry); +struct cattr { + dev_t rdev; + umode_t mode; + const char *link; +}; int ovl_create_real(struct inode *dir, struct dentry *newdentry, - struct kstat *stat, const char *link, + struct cattr *attr, struct dentry *hardlink, bool debug); void ovl_cleanup(struct inode *dir, struct dentry *dentry); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 4bd1e9c7246f..7da36ccda438 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -354,12 +354,9 @@ retry: strlen(OVL_WORKDIR_NAME)); if (!IS_ERR(work)) { - struct kstat stat = { - .mode = S_IFDIR | 0, - }; struct iattr attr = { .ia_valid = ATTR_MODE, - .ia_mode = stat.mode, + .ia_mode = S_IFDIR | 0, }; if (work->d_inode) { @@ -373,7 +370,9 @@ retry: goto retry; } - err = ovl_create_real(dir, work, &stat, NULL, NULL, true); + err = ovl_create_real(dir, work, + &(struct cattr){.mode = S_IFDIR | 0}, + NULL, true); if (err) goto out_dput; -- cgit v1.2.3 From 313684c48cc0e450ab303e1f82130ee2d0b50274 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Fri, 18 Nov 2016 23:02:46 +0800 Subject: ovl: fix return value of ovl_fill_super If kcalloc() failed, the return value of ovl_fill_super() is -EINVAL, not -ENOMEM. So this patch sets this value to -ENOMEM before calling kcalloc(), and sets it back to -EINVAL after calling kcalloc(). Signed-off-by: Geliang Tang Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 7da36ccda438..20f48abbb82f 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -774,10 +774,12 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) goto out_free_lowertmp; } + err = -ENOMEM; stack = kcalloc(stacklen, sizeof(struct path), GFP_KERNEL); if (!stack) goto out_free_lowertmp; + err = -EINVAL; lower = lowertmp; for (numlower = 0; numlower < stacklen; numlower++) { err = ovl_lower_dir(lower, &stack[numlower], ufs, -- cgit v1.2.3 From c3c8699664800a68600f1988302173067eaeaffa Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Thu, 8 Dec 2016 09:49:51 +0200 Subject: ovl: fix reStructuredText syntax errors in documentation - Fix broken long line block quote - Fix missing newline before bullets list - Use correct numbered list syntax Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- Documentation/filesystems/overlayfs.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/filesystems/overlayfs.txt b/Documentation/filesystems/overlayfs.txt index fb6f3070c6fe..634d03e20c2d 100644 --- a/Documentation/filesystems/overlayfs.txt +++ b/Documentation/filesystems/overlayfs.txt @@ -66,7 +66,7 @@ At mount time, the two directories given as mount options "lowerdir" and "upperdir" are combined into a merged directory: mount -t overlay overlay -olowerdir=/lower,upperdir=/upper,\ -workdir=/work /merged + workdir=/work /merged The "workdir" needs to be an empty directory on the same filesystem as upperdir. @@ -118,6 +118,7 @@ programs. seek offsets are assigned sequentially when the directories are read. Thus if + - read part of a directory - remember an offset, and close the directory - re-open the directory some time later @@ -137,12 +138,12 @@ When renaming a directory that is on the lower layer or merged (i.e. the directory was not created on the upper layer to start with) overlayfs can handle it in two different ways: -1) return EXDEV error: this error is returned by rename(2) when trying to +1. return EXDEV error: this error is returned by rename(2) when trying to move a file or directory across filesystem boundaries. Hence applications are usually prepared to hande this error (mv(1) for example recursively copies the directory tree). This is the default behavior. -2) If the "redirect_dir" feature is enabled, then the directory will be +2. If the "redirect_dir" feature is enabled, then the directory will be copied up (but not the contents). Then the "trusted.overlay.redirect" extended attribute is set to the path of the original location from the root of the overlay. Finally the directory is moved to the new -- cgit v1.2.3