summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/fuse/cuse.c4
-rw-r--r--fs/fuse/dev.c13
-rw-r--r--fs/fuse/dir.c30
-rw-r--r--fs/fuse/file.c52
-rw-r--r--fs/fuse/fuse_i.h8
-rw-r--r--fs/inode.c21
-rw-r--r--fs/internal.h2
-rw-r--r--fs/namespace.c64
-rw-r--r--fs/open.c8
-rw-r--r--fs/overlayfs/dir.c11
-rw-r--r--fs/overlayfs/inode.c14
-rw-r--r--fs/overlayfs/overlayfs.h8
-rw-r--r--fs/overlayfs/readdir.c383
-rw-r--r--fs/overlayfs/super.c11
-rw-r--r--fs/overlayfs/util.c24
-rw-r--r--fs/xattr.c9
16 files changed, 537 insertions, 125 deletions
diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c
index c5b6b7165489..e9e97803442a 100644
--- a/fs/fuse/cuse.c
+++ b/fs/fuse/cuse.c
@@ -90,7 +90,7 @@ static struct list_head *cuse_conntbl_head(dev_t devt)
static ssize_t cuse_read_iter(struct kiocb *kiocb, struct iov_iter *to)
{
- struct fuse_io_priv io = FUSE_IO_PRIV_SYNC(kiocb->ki_filp);
+ struct fuse_io_priv io = FUSE_IO_PRIV_SYNC(kiocb);
loff_t pos = 0;
return fuse_direct_io(&io, to, &pos, FUSE_DIO_CUSE);
@@ -98,7 +98,7 @@ static ssize_t cuse_read_iter(struct kiocb *kiocb, struct iov_iter *to)
static ssize_t cuse_write_iter(struct kiocb *kiocb, struct iov_iter *from)
{
- struct fuse_io_priv io = FUSE_IO_PRIV_SYNC(kiocb->ki_filp);
+ struct fuse_io_priv io = FUSE_IO_PRIV_SYNC(kiocb);
loff_t pos = 0;
/*
* No locking or generic_write_checks(), the server is
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index c16d00e53264..13c65dd2d37d 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -1222,9 +1222,6 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,
struct fuse_in *in;
unsigned reqsize;
- if (task_active_pid_ns(current) != fc->pid_ns)
- return -EIO;
-
restart:
spin_lock(&fiq->waitq.lock);
err = -EAGAIN;
@@ -1262,6 +1259,13 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,
in = &req->in;
reqsize = in->h.len;
+
+ if (task_active_pid_ns(current) != fc->pid_ns) {
+ rcu_read_lock();
+ in->h.pid = pid_vnr(find_pid_ns(in->h.pid, fc->pid_ns));
+ rcu_read_unlock();
+ }
+
/* If request is too large, reply with an error and restart the read */
if (nbytes < reqsize) {
req->out.h.error = -EIO;
@@ -1823,9 +1827,6 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud,
struct fuse_req *req;
struct fuse_out_header oh;
- if (task_active_pid_ns(current) != fc->pid_ns)
- return -EIO;
-
if (nbytes < sizeof(struct fuse_out_header))
return -EINVAL;
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 00800c07ba1c..622081b97426 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -923,33 +923,29 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
return err;
}
-int fuse_update_attributes(struct inode *inode, struct kstat *stat,
- struct file *file, bool *refreshed)
+static int fuse_update_get_attr(struct inode *inode, struct file *file,
+ struct kstat *stat)
{
struct fuse_inode *fi = get_fuse_inode(inode);
- int err;
- bool r;
+ int err = 0;
if (time_before64(fi->i_time, get_jiffies_64())) {
- r = true;
forget_all_cached_acls(inode);
err = fuse_do_getattr(inode, stat, file);
- } else {
- r = false;
- err = 0;
- if (stat) {
- generic_fillattr(inode, stat);
- stat->mode = fi->orig_i_mode;
- stat->ino = fi->orig_ino;
- }
+ } else if (stat) {
+ generic_fillattr(inode, stat);
+ stat->mode = fi->orig_i_mode;
+ stat->ino = fi->orig_ino;
}
- if (refreshed != NULL)
- *refreshed = r;
-
return err;
}
+int fuse_update_attributes(struct inode *inode, struct file *file)
+{
+ return fuse_update_get_attr(inode, file, NULL);
+}
+
int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid,
u64 child_nodeid, struct qstr *name)
{
@@ -1786,7 +1782,7 @@ static int fuse_getattr(const struct path *path, struct kstat *stat,
if (!fuse_allow_current_process(fc))
return -EACCES;
- return fuse_update_attributes(inode, stat, NULL, NULL);
+ return fuse_update_get_attr(inode, NULL, stat);
}
static const struct inode_operations fuse_dir_inode_operations = {
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index d66789804287..cb7dff5c45d7 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -645,7 +645,7 @@ static size_t fuse_async_req_send(struct fuse_conn *fc, struct fuse_req *req,
static size_t fuse_send_read(struct fuse_req *req, struct fuse_io_priv *io,
loff_t pos, size_t count, fl_owner_t owner)
{
- struct file *file = io->file;
+ struct file *file = io->iocb->ki_filp;
struct fuse_file *ff = file->private_data;
struct fuse_conn *fc = ff->fc;
@@ -707,7 +707,8 @@ static void fuse_short_read(struct fuse_req *req, struct inode *inode,
static int fuse_do_readpage(struct file *file, struct page *page)
{
- struct fuse_io_priv io = FUSE_IO_PRIV_SYNC(file);
+ struct kiocb iocb;
+ struct fuse_io_priv io;
struct inode *inode = page->mapping->host;
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_req *req;
@@ -735,6 +736,8 @@ static int fuse_do_readpage(struct file *file, struct page *page)
req->num_pages = 1;
req->pages[0] = page;
req->page_descs[0].length = count;
+ init_sync_kiocb(&iocb, file);
+ io = (struct fuse_io_priv) FUSE_IO_PRIV_SYNC(&iocb);
num_read = fuse_send_read(req, &io, pos, count, NULL);
err = req->out.h.error;
@@ -923,7 +926,7 @@ static ssize_t fuse_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
if (fc->auto_inval_data ||
(iocb->ki_pos + iov_iter_count(to) > i_size_read(inode))) {
int err;
- err = fuse_update_attributes(inode, NULL, iocb->ki_filp, NULL);
+ err = fuse_update_attributes(inode, iocb->ki_filp);
if (err)
return err;
}
@@ -957,13 +960,18 @@ static void fuse_write_fill(struct fuse_req *req, struct fuse_file *ff,
static size_t fuse_send_write(struct fuse_req *req, struct fuse_io_priv *io,
loff_t pos, size_t count, fl_owner_t owner)
{
- struct file *file = io->file;
+ struct kiocb *iocb = io->iocb;
+ struct file *file = iocb->ki_filp;
struct fuse_file *ff = file->private_data;
struct fuse_conn *fc = ff->fc;
struct fuse_write_in *inarg = &req->misc.write.in;
fuse_write_fill(req, ff, pos, count);
inarg->flags = file->f_flags;
+ if (iocb->ki_flags & IOCB_DSYNC)
+ inarg->flags |= O_DSYNC;
+ if (iocb->ki_flags & IOCB_SYNC)
+ inarg->flags |= O_SYNC;
if (owner != NULL) {
inarg->write_flags |= FUSE_WRITE_LOCKOWNER;
inarg->lock_owner = fuse_lock_owner_id(fc, owner);
@@ -993,14 +1001,14 @@ bool fuse_write_update_size(struct inode *inode, loff_t pos)
return ret;
}
-static size_t fuse_send_write_pages(struct fuse_req *req, struct file *file,
+static size_t fuse_send_write_pages(struct fuse_req *req, struct kiocb *iocb,
struct inode *inode, loff_t pos,
size_t count)
{
size_t res;
unsigned offset;
unsigned i;
- struct fuse_io_priv io = FUSE_IO_PRIV_SYNC(file);
+ struct fuse_io_priv io = FUSE_IO_PRIV_SYNC(iocb);
for (i = 0; i < req->num_pages; i++)
fuse_wait_on_page_writeback(inode, req->pages[i]->index);
@@ -1100,7 +1108,7 @@ static inline unsigned fuse_wr_pages(loff_t pos, size_t len)
FUSE_MAX_PAGES_PER_REQ);
}
-static ssize_t fuse_perform_write(struct file *file,
+static ssize_t fuse_perform_write(struct kiocb *iocb,
struct address_space *mapping,
struct iov_iter *ii, loff_t pos)
{
@@ -1133,7 +1141,7 @@ static ssize_t fuse_perform_write(struct file *file,
} else {
size_t num_written;
- num_written = fuse_send_write_pages(req, file, inode,
+ num_written = fuse_send_write_pages(req, iocb, inode,
pos, count);
err = req->out.h.error;
if (!err) {
@@ -1169,7 +1177,7 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
if (get_fuse_conn(inode)->writeback_cache) {
/* Update size (EOF optimization) and mode (SUID clearing) */
- err = fuse_update_attributes(mapping->host, NULL, file, NULL);
+ err = fuse_update_attributes(mapping->host, file);
if (err)
return err;
@@ -1201,7 +1209,7 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
pos += written;
- written_buffered = fuse_perform_write(file, mapping, from, pos);
+ written_buffered = fuse_perform_write(iocb, mapping, from, pos);
if (written_buffered < 0) {
err = written_buffered;
goto out;
@@ -1220,13 +1228,15 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
written += written_buffered;
iocb->ki_pos = pos + written_buffered;
} else {
- written = fuse_perform_write(file, mapping, from, iocb->ki_pos);
+ written = fuse_perform_write(iocb, mapping, from, iocb->ki_pos);
if (written >= 0)
iocb->ki_pos += written;
}
out:
current->backing_dev_info = NULL;
inode_unlock(inode);
+ if (written > 0)
+ written = generic_write_sync(iocb, written);
return written ? written : err;
}
@@ -1317,7 +1327,7 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,
{
int write = flags & FUSE_DIO_WRITE;
int cuse = flags & FUSE_DIO_CUSE;
- struct file *file = io->file;
+ struct file *file = io->iocb->ki_filp;
struct inode *inode = file->f_mapping->host;
struct fuse_file *ff = file->private_data;
struct fuse_conn *fc = ff->fc;
@@ -1399,8 +1409,7 @@ static ssize_t __fuse_direct_read(struct fuse_io_priv *io,
loff_t *ppos)
{
ssize_t res;
- struct file *file = io->file;
- struct inode *inode = file_inode(file);
+ struct inode *inode = file_inode(io->iocb->ki_filp);
if (is_bad_inode(inode))
return -EIO;
@@ -1414,15 +1423,14 @@ static ssize_t __fuse_direct_read(struct fuse_io_priv *io,
static ssize_t fuse_direct_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
- struct fuse_io_priv io = FUSE_IO_PRIV_SYNC(iocb->ki_filp);
+ struct fuse_io_priv io = FUSE_IO_PRIV_SYNC(iocb);
return __fuse_direct_read(&io, to, &iocb->ki_pos);
}
static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
- struct file *file = iocb->ki_filp;
- struct inode *inode = file_inode(file);
- struct fuse_io_priv io = FUSE_IO_PRIV_SYNC(file);
+ struct inode *inode = file_inode(iocb->ki_filp);
+ struct fuse_io_priv io = FUSE_IO_PRIV_SYNC(iocb);
ssize_t res;
if (is_bad_inode(inode))
@@ -2181,9 +2189,6 @@ static int fuse_setlk(struct file *file, struct file_lock *fl, int flock)
if ((fl->fl_flags & FL_CLOSE_POSIX) == FL_CLOSE_POSIX)
return 0;
- if (pid && pid_nr == 0)
- return -EOVERFLOW;
-
fuse_lk_fill(&args, file, fl, opcode, pid_nr, flock, &inarg);
err = fuse_simple_request(fc, &args);
@@ -2303,7 +2308,7 @@ static loff_t fuse_lseek(struct file *file, loff_t offset, int whence)
return vfs_setpos(file, outarg.offset, inode->i_sb->s_maxbytes);
fallback:
- err = fuse_update_attributes(inode, NULL, file, NULL);
+ err = fuse_update_attributes(inode, file);
if (!err)
return generic_file_llseek(file, offset, whence);
else
@@ -2323,7 +2328,7 @@ static loff_t fuse_file_llseek(struct file *file, loff_t offset, int whence)
break;
case SEEK_END:
inode_lock(inode);
- retval = fuse_update_attributes(inode, NULL, file, NULL);
+ retval = fuse_update_attributes(inode, file);
if (!retval)
retval = generic_file_llseek(file, offset, whence);
inode_unlock(inode);
@@ -2874,7 +2879,6 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
io->offset = offset;
io->write = (iov_iter_rw(iter) == WRITE);
io->err = 0;
- io->file = file;
/*
* By default, we want to optimize all I/Os with async request
* submission to the client filesystem if supported.
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index bd4d2a3e1ec1..d5773ca67ad2 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -252,16 +252,15 @@ struct fuse_io_priv {
bool should_dirty;
int err;
struct kiocb *iocb;
- struct file *file;
struct completion *done;
bool blocking;
};
-#define FUSE_IO_PRIV_SYNC(f) \
+#define FUSE_IO_PRIV_SYNC(i) \
{ \
.refcnt = KREF_INIT(1), \
.async = 0, \
- .file = f, \
+ .iocb = i, \
}
/**
@@ -905,8 +904,7 @@ u64 fuse_lock_owner_id(struct fuse_conn *fc, fl_owner_t id);
void fuse_update_ctime(struct inode *inode);
-int fuse_update_attributes(struct inode *inode, struct kstat *stat,
- struct file *file, bool *refreshed);
+int fuse_update_attributes(struct inode *inode, struct file *file);
void fuse_flush_writepages(struct inode *inode);
diff --git a/fs/inode.c b/fs/inode.c
index 210054157a49..d1e35b53bb23 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -1570,11 +1570,24 @@ EXPORT_SYMBOL(bmap);
static void update_ovl_inode_times(struct dentry *dentry, struct inode *inode,
bool rcu)
{
- if (!rcu) {
- struct inode *realinode = d_real_inode(dentry);
+ struct dentry *upperdentry;
- if (unlikely(inode != realinode) &&
- (!timespec_equal(&inode->i_mtime, &realinode->i_mtime) ||
+ /*
+ * Nothing to do if in rcu or if non-overlayfs
+ */
+ if (rcu || likely(!(dentry->d_flags & DCACHE_OP_REAL)))
+ return;
+
+ upperdentry = d_real(dentry, NULL, 0, D_REAL_UPPER);
+
+ /*
+ * If file is on lower then we can't update atime, so no worries about
+ * stale mtime/ctime.
+ */
+ if (upperdentry) {
+ struct inode *realinode = d_inode(upperdentry);
+
+ if ((!timespec_equal(&inode->i_mtime, &realinode->i_mtime) ||
!timespec_equal(&inode->i_ctime, &realinode->i_ctime))) {
inode->i_mtime = realinode->i_mtime;
inode->i_ctime = realinode->i_ctime;
diff --git a/fs/internal.h b/fs/internal.h
index fedfe94d84ba..48cee21b4f14 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -71,8 +71,10 @@ extern void __init mnt_init(void);
extern int __mnt_want_write(struct vfsmount *);
extern int __mnt_want_write_file(struct file *);
+extern int mnt_want_write_file_path(struct file *);
extern void __mnt_drop_write(struct vfsmount *);
extern void __mnt_drop_write_file(struct file *);
+extern void mnt_drop_write_file_path(struct file *);
/*
* fs_struct.c
diff --git a/fs/namespace.c b/fs/namespace.c
index f8893dc6a989..df0f7521979a 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -431,13 +431,18 @@ int __mnt_want_write_file(struct file *file)
}
/**
- * mnt_want_write_file - get write access to a file's mount
+ * mnt_want_write_file_path - get write access to a file's mount
* @file: the file who's mount on which to take a write
*
* This is like mnt_want_write, but it takes a file and can
* do some optimisations if the file is open for write already
+ *
+ * Called by the vfs for cases when we have an open file at hand, but will do an
+ * inode operation on it (important distinction for files opened on overlayfs,
+ * since the file operations will come from the real underlying file, while
+ * inode operations come from the overlay).
*/
-int mnt_want_write_file(struct file *file)
+int mnt_want_write_file_path(struct file *file)
{
int ret;
@@ -447,6 +452,53 @@ int mnt_want_write_file(struct file *file)
sb_end_write(file->f_path.mnt->mnt_sb);
return ret;
}
+
+static inline int may_write_real(struct file *file)
+{
+ struct dentry *dentry = file->f_path.dentry;
+ struct dentry *upperdentry;
+
+ /* Writable file? */
+ if (file->f_mode & FMODE_WRITER)
+ return 0;
+
+ /* Not overlayfs? */
+ if (likely(!(dentry->d_flags & DCACHE_OP_REAL)))
+ return 0;
+
+ /* File refers to upper, writable layer? */
+ upperdentry = d_real(dentry, NULL, 0, D_REAL_UPPER);
+ if (upperdentry && file_inode(file) == d_inode(upperdentry))
+ return 0;
+
+ /* Lower layer: can't write to real file, sorry... */
+ return -EPERM;
+}
+
+/**
+ * mnt_want_write_file - get write access to a file's mount
+ * @file: the file who's mount on which to take a write
+ *
+ * This is like mnt_want_write, but it takes a file and can
+ * do some optimisations if the file is open for write already
+ *
+ * Mostly called by filesystems from their ioctl operation before performing
+ * modification. On overlayfs this needs to check if the file is on a read-only
+ * lower layer and deny access in that case.
+ */
+int mnt_want_write_file(struct file *file)
+{
+ int ret;
+
+ ret = may_write_real(file);
+ if (!ret) {
+ sb_start_write(file_inode(file)->i_sb);
+ ret = __mnt_want_write_file(file);
+ if (ret)
+ sb_end_write(file_inode(file)->i_sb);
+ }
+ return ret;
+}
EXPORT_SYMBOL_GPL(mnt_want_write_file);
/**
@@ -484,10 +536,16 @@ void __mnt_drop_write_file(struct file *file)
__mnt_drop_write(file->f_path.mnt);
}
-void mnt_drop_write_file(struct file *file)
+void mnt_drop_write_file_path(struct file *file)
{
mnt_drop_write(file->f_path.mnt);
}
+
+void mnt_drop_write_file(struct file *file)
+{
+ __mnt_drop_write(file->f_path.mnt);
+ sb_end_write(file_inode(file)->i_sb);
+}
EXPORT_SYMBOL(mnt_drop_write_file);
static int mnt_make_readonly(struct mount *mnt)
diff --git a/fs/open.c b/fs/open.c
index 35bb784763a4..7ea118471dce 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -96,7 +96,7 @@ long vfs_truncate(const struct path *path, loff_t length)
* write access on the upper inode, not on the overlay inode. For
* non-overlay filesystems d_real() is an identity function.
*/
- upperdentry = d_real(path->dentry, NULL, O_WRONLY);
+ upperdentry = d_real(path->dentry, NULL, O_WRONLY, 0);
error = PTR_ERR(upperdentry);
if (IS_ERR(upperdentry))
goto mnt_drop_write_and_out;
@@ -670,12 +670,12 @@ SYSCALL_DEFINE3(fchown, unsigned int, fd, uid_t, user, gid_t, group)
if (!f.file)
goto out;
- error = mnt_want_write_file(f.file);
+ error = mnt_want_write_file_path(f.file);
if (error)
goto out_fput;
audit_file(f.file);
error = chown_common(&f.file->f_path, user, group);
- mnt_drop_write_file(f.file);
+ mnt_drop_write_file_path(f.file);
out_fput:
fdput(f);
out:
@@ -857,7 +857,7 @@ EXPORT_SYMBOL(file_path);
int vfs_open(const struct path *path, struct file *file,
const struct cred *cred)
{
- struct dentry *dentry = d_real(path->dentry, NULL, file->f_flags);
+ struct dentry *dentry = d_real(path->dentry, NULL, file->f_flags, 0);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index 48b70e6490f3..9cb0c80e5967 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -155,7 +155,7 @@ static int ovl_set_opaque(struct dentry *dentry, struct dentry *upperdentry)
static void ovl_instantiate(struct dentry *dentry, struct inode *inode,
struct dentry *newdentry, bool hardlink)
{
- ovl_dentry_version_inc(dentry->d_parent);
+ ovl_dentry_version_inc(dentry->d_parent, false);
ovl_dentry_set_upper_alias(dentry);
if (!hardlink) {
ovl_inode_update(inode, newdentry);
@@ -692,7 +692,7 @@ static int ovl_remove_and_whiteout(struct dentry *dentry, bool is_dir)
if (flags)
ovl_cleanup(wdir, upper);
- ovl_dentry_version_inc(dentry->d_parent);
+ ovl_dentry_version_inc(dentry->d_parent, true);
out_d_drop:
d_drop(dentry);
dput(whiteout);
@@ -742,7 +742,7 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir)
err = vfs_rmdir(dir, upper);
else
err = vfs_unlink(dir, upper, NULL);
- ovl_dentry_version_inc(dentry->d_parent);
+ ovl_dentry_version_inc(dentry->d_parent, ovl_type_origin(dentry));
/*
* Keeping this dentry hashed would mean having to release
@@ -1089,8 +1089,9 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,
drop_nlink(d_inode(new));
}
- ovl_dentry_version_inc(old->d_parent);
- ovl_dentry_version_inc(new->d_parent);
+ ovl_dentry_version_inc(old->d_parent,
+ !overwrite && ovl_type_origin(new));
+ ovl_dentry_version_inc(new->d_parent, ovl_type_origin(old));
out_dput:
dput(newdentry);
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index 5bc71642b226..a619addecafc 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -498,6 +498,9 @@ static int ovl_set_nlink_common(struct dentry *dentry,
len = snprintf(buf, sizeof(buf), format,
(int) (inode->i_nlink - realinode->i_nlink));
+ if (WARN_ON(len >= sizeof(buf)))
+ return -EIO;
+
return ovl_do_setxattr(ovl_dentry_upper(dentry),
OVL_XATTR_NLINK, buf, len, 0);
}
@@ -576,10 +579,13 @@ static int ovl_inode_set(struct inode *inode, void *data)
static bool ovl_verify_inode(struct inode *inode, struct dentry *lowerdentry,
struct dentry *upperdentry)
{
- struct inode *lowerinode = lowerdentry ? d_inode(lowerdentry) : NULL;
-
- /* Lower (origin) inode must match, even if NULL */
- if (ovl_inode_lower(inode) != lowerinode)
+ /*
+ * Allow non-NULL lower inode in ovl_inode even if lowerdentry is NULL.
+ * This happens when finding a copied up overlay inode for a renamed
+ * or hardlinked overlay dentry and lower dentry cannot be followed
+ * by origin because lower fs does not support file handles.
+ */
+ if (lowerdentry && ovl_inode_lower(inode) != d_inode(lowerdentry))
return false;
/*
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index e927a62c97ae..d4e8c1a08fb0 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -204,8 +204,8 @@ struct dentry *ovl_i_dentry_upper(struct inode *inode);
struct inode *ovl_inode_upper(struct inode *inode);
struct inode *ovl_inode_lower(struct inode *inode);
struct inode *ovl_inode_real(struct inode *inode);
-struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry);
-void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache);
+struct ovl_dir_cache *ovl_dir_cache(struct inode *inode);
+void ovl_set_dir_cache(struct inode *inode, 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);
@@ -217,7 +217,7 @@ void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect);
void ovl_inode_init(struct inode *inode, struct dentry *upperdentry,
struct dentry *lowerdentry);
void ovl_inode_update(struct inode *inode, struct dentry *upperdentry);
-void ovl_dentry_version_inc(struct dentry *dentry);
+void ovl_dentry_version_inc(struct dentry *dentry, bool impurity);
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);
@@ -229,6 +229,7 @@ int ovl_check_setxattr(struct dentry *dentry, struct dentry *upperdentry,
int xerr);
int ovl_set_impure(struct dentry *dentry, struct dentry *upperdentry);
void ovl_set_flag(unsigned long flag, struct inode *inode);
+void ovl_clear_flag(unsigned long flag, struct inode *inode);
bool ovl_test_flag(unsigned long flag, struct inode *inode);
bool ovl_inuse_trylock(struct dentry *dentry);
void ovl_inuse_unlock(struct dentry *dentry);
@@ -256,6 +257,7 @@ extern const struct file_operations ovl_dir_operations;
int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list);
void ovl_cleanup_whiteouts(struct dentry *upper, struct list_head *list);
void ovl_cache_free(struct list_head *list);
+void ovl_dir_cache_free(struct inode *inode);
int ovl_check_d_type_supported(struct path *realpath);
void ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt,
struct dentry *dentry, int level);
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index f0fd3adb1693..62e9b22a2077 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -15,11 +15,13 @@
#include <linux/rbtree.h>
#include <linux/security.h>
#include <linux/cred.h>
+#include <linux/ratelimit.h>
#include "overlayfs.h"
struct ovl_cache_entry {
unsigned int len;
unsigned int type;
+ u64 real_ino;
u64 ino;
struct list_head l_node;
struct rb_node node;
@@ -32,18 +34,20 @@ struct ovl_dir_cache {
long refcount;
u64 version;
struct list_head entries;
+ struct rb_root root;
};
struct ovl_readdir_data {
struct dir_context ctx;
struct dentry *dentry;
bool is_lowest;
- struct rb_root root;
+ struct rb_root *root;
struct list_head *list;
struct list_head middle;
struct ovl_cache_entry *first_maybe_whiteout;
int count;
int err;
+ bool is_upper;
bool d_type_supported;
};
@@ -58,7 +62,33 @@ struct ovl_dir_file {
static struct ovl_cache_entry *ovl_cache_entry_from_node(struct rb_node *n)
{
- return container_of(n, struct ovl_cache_entry, node);
+ return rb_entry(n, struct ovl_cache_entry, node);
+}
+
+static bool ovl_cache_entry_find_link(const char *name, int len,
+ struct rb_node ***link,
+ struct rb_node **parent)
+{
+ bool found = false;
+ struct rb_node **newp = *link;
+
+ while (!found && *newp) {
+ int cmp;
+ struct ovl_cache_entry *tmp;
+
+ *parent = *newp;
+ tmp = ovl_cache_entry_from_node(*newp);
+ cmp = strncmp(name, tmp->name, len);
+ if (cmp > 0)
+ newp = &tmp->node.rb_right;
+ else if (cmp < 0 || len < tmp->len)
+ newp = &tmp->node.rb_left;
+ else
+ found = true;
+ }
+ *link = newp;
+
+ return found;
}
static struct ovl_cache_entry *ovl_cache_entry_find(struct rb_root *root,
@@ -82,6 +112,32 @@ static struct ovl_cache_entry *ovl_cache_entry_find(struct rb_root *root,
return NULL;
}
+static bool ovl_calc_d_ino(struct ovl_readdir_data *rdd,
+ struct ovl_cache_entry *p)
+{
+ /* Don't care if not doing ovl_iter() */
+ if (!rdd->dentry)
+ return false;
+
+ /* Always recalc d_ino for parent */
+ if (strcmp(p->name, "..") == 0)
+ return true;
+
+ /* If this is lower, then native d_ino will do */
+ if (!rdd->is_upper)
+ return false;
+
+ /*
+ * Recalc d_ino for '.' and for all entries if dir is impure (contains
+ * copied up entries)
+ */
+ if ((p->name[0] == '.' && p->len == 1) ||
+ ovl_test_flag(OVL_IMPURE, d_inode(rdd->dentry)))
+ return true;
+
+ return false;
+}
+
static struct ovl_cache_entry *ovl_cache_entry_new(struct ovl_readdir_data *rdd,
const char *name, int len,
u64 ino, unsigned int d_type)
@@ -97,7 +153,11 @@ static struct ovl_cache_entry *ovl_cache_entry_new(struct ovl_readdir_data *rdd,
p->name[len] = '\0';
p->len = len;
p->type = d_type;
+ p->real_ino = ino;
p->ino = ino;
+ /* Defer setting d_ino for upper entry to ovl_iterate() */
+ if (ovl_calc_d_ino(rdd, p))
+ p->ino = 0;
p->is_whiteout = false;
if (d_type == DT_CHR) {
@@ -111,32 +171,22 @@ static int ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd,
const char *name, int len, u64 ino,
unsigned int d_type)
{
- struct rb_node **newp = &rdd->root.rb_node;
+ struct rb_node **newp = &rdd->root->rb_node;
struct rb_node *parent = NULL;
struct ovl_cache_entry *p;
- while (*newp) {
- int cmp;
- struct ovl_cache_entry *tmp;
-
- parent = *newp;
- tmp = ovl_cache_entry_from_node(*newp);
- cmp = strncmp(name, tmp->name, len);
- if (cmp > 0)
- newp = &tmp->node.rb_right;
- else if (cmp < 0 || len < tmp->len)
- newp = &tmp->node.rb_left;
- else
- return 0;
- }
+ if (ovl_cache_entry_find_link(name, len, &newp, &parent))
+ return 0;
p = ovl_cache_entry_new(rdd, name, len, ino, d_type);
- if (p == NULL)
+ if (p == NULL) {
+ rdd->err = -ENOMEM;
return -ENOMEM;
+ }
list_add_tail(&p->l_node, rdd->list);
rb_link_node(&p->node, parent, newp);
- rb_insert_color(&p->node, &rdd->root);
+ rb_insert_color(&p->node, rdd->root);
return 0;
}
@@ -147,7 +197,7 @@ static int ovl_fill_lowest(struct ovl_readdir_data *rdd,
{
struct ovl_cache_entry *p;
- p = ovl_cache_entry_find(&rdd->root, name, namelen);
+ p = ovl_cache_entry_find(rdd->root, name, namelen);
if (p) {
list_move_tail(&p->l_node, &rdd->middle);
} else {
@@ -172,6 +222,16 @@ void ovl_cache_free(struct list_head *list)
INIT_LIST_HEAD(list);
}
+void ovl_dir_cache_free(struct inode *inode)
+{
+ struct ovl_dir_cache *cache = ovl_dir_cache(inode);
+
+ if (cache) {
+ ovl_cache_free(&cache->entries);
+ kfree(cache);
+ }
+}
+
static void ovl_cache_put(struct ovl_dir_file *od, struct dentry *dentry)
{
struct ovl_dir_cache *cache = od->cache;
@@ -179,8 +239,8 @@ static void ovl_cache_put(struct ovl_dir_file *od, struct dentry *dentry)
WARN_ON(cache->refcount <= 0);
cache->refcount--;
if (!cache->refcount) {
- if (ovl_dir_cache(dentry) == cache)
- ovl_set_dir_cache(dentry, NULL);
+ if (ovl_dir_cache(d_inode(dentry)) == cache)
+ ovl_set_dir_cache(d_inode(dentry), NULL);
ovl_cache_free(&cache->entries);
kfree(cache);
@@ -273,7 +333,8 @@ static void ovl_dir_reset(struct file *file)
od->is_real = false;
}
-static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list)
+static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list,
+ struct rb_root *root)
{
int err;
struct path realpath;
@@ -281,13 +342,14 @@ static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list)
.ctx.actor = ovl_fill_merge,
.dentry = dentry,
.list = list,
- .root = RB_ROOT,
+ .root = root,
.is_lowest = false,
};
int idx, next;
for (idx = 0; idx != -1; idx = next) {
next = ovl_path_next(idx, dentry, &realpath);
+ rdd.is_upper = ovl_dentry_upper(dentry) == realpath.dentry;
if (next != -1) {
err = ovl_dir_read(&realpath, &rdd);
@@ -326,12 +388,13 @@ static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry)
int res;
struct ovl_dir_cache *cache;
- cache = ovl_dir_cache(dentry);
+ cache = ovl_dir_cache(d_inode(dentry));
if (cache && ovl_dentry_version_get(dentry) == cache->version) {
+ WARN_ON(!cache->refcount);
cache->refcount++;
return cache;
}
- ovl_set_dir_cache(dentry, NULL);
+ ovl_set_dir_cache(d_inode(dentry), NULL);
cache = kzalloc(sizeof(struct ovl_dir_cache), GFP_KERNEL);
if (!cache)
@@ -339,8 +402,9 @@ static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry)
cache->refcount = 1;
INIT_LIST_HEAD(&cache->entries);
+ cache->root = RB_ROOT;
- res = ovl_dir_read_merged(dentry, &cache->entries);
+ res = ovl_dir_read_merged(dentry, &cache->entries, &cache->root);
if (res) {
ovl_cache_free(&cache->entries);
kfree(cache);
@@ -348,22 +412,266 @@ static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry)
}
cache->version = ovl_dentry_version_get(dentry);
- ovl_set_dir_cache(dentry, cache);
+ ovl_set_dir_cache(d_inode(dentry), cache);
return cache;
}
+/*
+ * Set d_ino for upper entries. Non-upper entries should always report
+ * the uppermost real inode ino and should not call this function.
+ *
+ * When not all layer are on same fs, report real ino also for upper.
+ *
+ * When all layers are on the same fs, and upper has a reference to
+ * copy up origin, call vfs_getattr() on the overlay entry to make
+ * sure that d_ino will be consistent with st_ino from stat(2).
+ */
+static int ovl_cache_update_ino(struct path *path, struct ovl_cache_entry *p)
+
+{
+ struct dentry *dir = path->dentry;
+ struct dentry *this = NULL;
+ enum ovl_path_type type;
+ u64 ino = p->real_ino;
+ int err = 0;
+
+ if (!ovl_same_sb(dir->d_sb))
+ goto out;
+
+ if (p->name[0] == '.') {
+ if (p->len == 1) {
+ this = dget(dir);
+ goto get;
+ }
+ if (p->len == 2 && p->name[1] == '.') {
+ /* we shall not be moved */
+ this = dget(dir->d_parent);
+ goto get;
+ }
+ }
+ this = lookup_one_len(p->name, dir, p->len);
+ if (IS_ERR_OR_NULL(this) || !this->d_inode) {
+ if (IS_ERR(this)) {
+ err = PTR_ERR(this);
+ this = NULL;
+ goto fail;
+ }
+ goto out;
+ }
+
+get:
+ type = ovl_path_type(this);
+ if (OVL_TYPE_ORIGIN(type)) {
+ struct kstat stat;
+ struct path statpath = *path;
+
+ statpath.dentry = this;
+ err = vfs_getattr(&statpath, &stat, STATX_INO, 0);
+ if (err)
+ goto fail;
+
+ WARN_ON_ONCE(dir->d_sb->s_dev != stat.dev);
+ ino = stat.ino;
+ }
+
+out:
+ p->ino = ino;
+ dput(this);
+ return err;
+
+fail:
+ pr_warn_ratelimited("overlay: failed to look up (%s) for ino (%i)\n",
+ p->name, err);
+ goto out;
+}
+
+static int ovl_fill_plain(struct dir_context *ctx, const char *name,
+ int namelen, loff_t offset, u64 ino,
+ unsigned int d_type)
+{
+ struct ovl_cache_entry *p;
+ struct ovl_readdir_data *rdd =
+ container_of(ctx, struct ovl_readdir_data, ctx);
+
+ rdd->count++;
+ p = ovl_cache_entry_new(rdd, name, namelen, ino, d_type);
+ if (p == NULL) {
+ rdd->err = -ENOMEM;
+ return -ENOMEM;
+ }
+ list_add_tail(&p->l_node, rdd->list);
+
+ return 0;
+}
+
+static int ovl_dir_read_impure(struct path *path, struct list_head *list,
+ struct rb_root *root)
+{
+ int err;
+ struct path realpath;
+ struct ovl_cache_entry *p, *n;
+ struct ovl_readdir_data rdd = {
+ .ctx.actor = ovl_fill_plain,
+ .list = list,
+ .root = root,
+ };
+
+ INIT_LIST_HEAD(list);
+ *root = RB_ROOT;
+ ovl_path_upper(path->dentry, &realpath);
+
+ err = ovl_dir_read(&realpath, &rdd);
+ if (err)
+ return err;
+
+ list_for_each_entry_safe(p, n, list, l_node) {
+ if (strcmp(p->name, ".") != 0 &&
+ strcmp(p->name, "..") != 0) {
+ err = ovl_cache_update_ino(path, p);
+ if (err)
+ return err;
+ }
+ if (p->ino == p->real_ino) {
+ list_del(&p->l_node);
+ kfree(p);
+ } else {
+ struct rb_node **newp = &root->rb_node;
+ struct rb_node *parent = NULL;
+
+ if (WARN_ON(ovl_cache_entry_find_link(p->name, p->len,
+ &newp, &parent)))
+ return -EIO;
+
+ rb_link_node(&p->node, parent, newp);
+ rb_insert_color(&p->node, root);
+ }
+ }
+ return 0;
+}
+
+static struct ovl_dir_cache *ovl_cache_get_impure(struct path *path)
+{
+ int res;
+ struct dentry *dentry = path->dentry;
+ struct ovl_dir_cache *cache;
+
+ cache = ovl_dir_cache(d_inode(dentry));
+ if (cache && ovl_dentry_version_get(dentry) == cache->version)
+ return cache;
+
+ /* Impure cache is not refcounted, free it here */
+ ovl_dir_cache_free(d_inode(dentry));
+ ovl_set_dir_cache(d_inode(dentry), NULL);
+
+ cache = kzalloc(sizeof(struct ovl_dir_cache), GFP_KERNEL);
+ if (!cache)
+ return ERR_PTR(-ENOMEM);
+
+ res = ovl_dir_read_impure(path, &cache->entries, &cache->root);
+ if (res) {
+ ovl_cache_free(&cache->entries);
+ kfree(cache);
+ return ERR_PTR(res);
+ }
+ if (list_empty(&cache->entries)) {
+ /* Good oportunity to get rid of an unnecessary "impure" flag */
+ ovl_do_removexattr(ovl_dentry_upper(dentry), OVL_XATTR_IMPURE);
+ ovl_clear_flag(OVL_IMPURE, d_inode(dentry));
+ kfree(cache);
+ return NULL;
+ }
+
+ cache->version = ovl_dentry_version_get(dentry);
+ ovl_set_dir_cache(d_inode(dentry), cache);
+
+ return cache;
+}
+
+struct ovl_readdir_translate {
+ struct dir_context *orig_ctx;
+ struct ovl_dir_cache *cache;
+ struct dir_context ctx;
+ u64 parent_ino;
+};
+
+static int ovl_fill_real(struct dir_context *ctx, const char *name,
+ int namelen, loff_t offset, u64 ino,
+ unsigned int d_type)
+{
+ struct ovl_readdir_translate *rdt =
+ container_of(ctx, struct ovl_readdir_translate, ctx);
+ struct dir_context *orig_ctx = rdt->orig_ctx;
+
+ if (rdt->parent_ino && strcmp(name, "..") == 0)
+ ino = rdt->parent_ino;
+ else if (rdt->cache) {
+ struct ovl_cache_entry *p;
+
+ p = ovl_cache_entry_find(&rdt->cache->root, name, namelen);
+ if (p)
+ ino = p->ino;
+ }
+
+ return orig_ctx->actor(orig_ctx, name, namelen, offset, ino, d_type);
+}
+
+static int ovl_iterate_real(struct file *file, struct dir_context *ctx)
+{
+ int err;
+ struct ovl_dir_file *od = file->private_data;
+ struct dentry *dir = file->f_path.dentry;
+ struct ovl_readdir_translate rdt = {
+ .ctx.actor = ovl_fill_real,
+ .orig_ctx = ctx,
+ };
+
+ if (OVL_TYPE_MERGE(ovl_path_type(dir->d_parent))) {
+ struct kstat stat;
+ struct path statpath = file->f_path;
+
+ statpath.dentry = dir->d_parent;
+ err = vfs_getattr(&statpath, &stat, STATX_INO, 0);
+ if (err)
+ return err;
+
+ WARN_ON_ONCE(dir->d_sb->s_dev != stat.dev);
+ rdt.parent_ino = stat.ino;
+ }
+
+ if (ovl_test_flag(OVL_IMPURE, d_inode(dir))) {
+ rdt.cache = ovl_cache_get_impure(&file->f_path);
+ if (IS_ERR(rdt.cache))
+ return PTR_ERR(rdt.cache);
+ }
+
+ return iterate_dir(od->realfile, &rdt.ctx);
+}
+
+
static int ovl_iterate(struct file *file, struct dir_context *ctx)
{
struct ovl_dir_file *od = file->private_data;
struct dentry *dentry = file->f_path.dentry;
struct ovl_cache_entry *p;
+ int err;
if (!ctx->pos)
ovl_dir_reset(file);
- if (od->is_real)
+ if (od->is_real) {
+ /*
+ * If parent is merge, then need to adjust d_ino for '..', if
+ * dir is impure then need to adjust d_ino for copied up
+ * entries.
+ */
+ if (ovl_same_sb(dentry->d_sb) &&
+ (ovl_test_flag(OVL_IMPURE, d_inode(dentry)) ||
+ OVL_TYPE_MERGE(ovl_path_type(dentry->d_parent)))) {
+ return ovl_iterate_real(file, ctx);
+ }
return iterate_dir(od->realfile, ctx);
+ }
if (!od->cache) {
struct ovl_dir_cache *cache;
@@ -378,9 +686,15 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx)
while (od->cursor != &od->cache->entries) {
p = list_entry(od->cursor, struct ovl_cache_entry, l_node);
- if (!p->is_whiteout)
+ if (!p->is_whiteout) {
+ if (!p->ino) {
+ err = ovl_cache_update_ino(&file->f_path, p);
+ if (err)
+ return err;
+ }
if (!dir_emit(ctx, p->name, p->len, p->ino, p->type))
break;
+ }
od->cursor = p->l_node.next;
ctx->pos++;
}
@@ -522,8 +836,9 @@ int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list)
{
int err;
struct ovl_cache_entry *p;
+ struct rb_root root = RB_ROOT;
- err = ovl_dir_read_merged(dentry, list);
+ err = ovl_dir_read_merged(dentry, list, &root);
if (err)
return err;
@@ -612,12 +927,13 @@ static void ovl_workdir_cleanup_recurse(struct path *path, int level)
int err;
struct inode *dir = path->dentry->d_inode;
LIST_HEAD(list);
+ struct rb_root root = RB_ROOT;
struct ovl_cache_entry *p;
struct ovl_readdir_data rdd = {
.ctx.actor = ovl_fill_merge,
.dentry = NULL,
.list = &list,
- .root = RB_ROOT,
+ .root = &root,
.is_lowest = false,
};
@@ -675,12 +991,13 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt,
struct inode *dir = dentry->d_inode;
struct path path = { .mnt = mnt, .dentry = dentry };
LIST_HEAD(list);
+ struct rb_root root = RB_ROOT;
struct ovl_cache_entry *p;
struct ovl_readdir_data rdd = {
.ctx.actor = ovl_fill_merge,
.dentry = NULL,
.list = &list,
- .root = RB_ROOT,
+ .root = &root,
.is_lowest = false,
};
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index d86e89f97201..cd49c0298ddf 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -70,20 +70,20 @@ static int ovl_check_append_only(struct inode *inode, int flag)
static struct dentry *ovl_d_real(struct dentry *dentry,
const struct inode *inode,
- unsigned int open_flags)
+ unsigned int open_flags, unsigned int flags)
{
struct dentry *real;
int err;
+ if (flags & D_REAL_UPPER)
+ return ovl_dentry_upper(dentry);
+
if (!d_is_reg(dentry)) {
if (!inode || inode == d_inode(dentry))
return dentry;
goto bug;
}
- if (d_is_negative(dentry))
- return dentry;
-
if (open_flags) {
err = ovl_open_maybe_copy_up(dentry, open_flags);
if (err)
@@ -105,7 +105,7 @@ static struct dentry *ovl_d_real(struct dentry *dentry,
goto bug;
/* Handle recursion */
- real = d_real(real, inode, open_flags);
+ real = d_real(real, inode, open_flags, 0);
if (!inode || inode == d_inode(real))
return real;
@@ -198,6 +198,7 @@ static void ovl_destroy_inode(struct inode *inode)
dput(oi->__upperdentry);
kfree(oi->redirect);
+ ovl_dir_cache_free(inode);
mutex_destroy(&oi->lock);
call_rcu(&inode->i_rcu, ovl_i_callback);
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index f46ad75dc96a..117794582f9f 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -180,14 +180,14 @@ struct inode *ovl_inode_real(struct inode *inode)
}
-struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry)
+struct ovl_dir_cache *ovl_dir_cache(struct inode *inode)
{
- return OVL_I(d_inode(dentry))->cache;
+ return OVL_I(inode)->cache;
}
-void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache)
+void ovl_set_dir_cache(struct inode *inode, struct ovl_dir_cache *cache)
{
- OVL_I(d_inode(dentry))->cache = cache;
+ OVL_I(inode)->cache = cache;
}
bool ovl_dentry_is_opaque(struct dentry *dentry)
@@ -275,12 +275,19 @@ void ovl_inode_update(struct inode *inode, struct dentry *upperdentry)
}
}
-void ovl_dentry_version_inc(struct dentry *dentry)
+void ovl_dentry_version_inc(struct dentry *dentry, bool impurity)
{
struct inode *inode = d_inode(dentry);
WARN_ON(!inode_is_locked(inode));
- OVL_I(inode)->version++;
+ /*
+ * Version is used by readdir code to keep cache consistent. For merge
+ * dirs all changes need to be noted. For non-merge dirs, cache only
+ * contains impure (ones which have been copied up and have origins)
+ * entries, so only need to note changes to impure entries.
+ */
+ if (OVL_TYPE_MERGE(ovl_path_type(dentry)) || impurity)
+ OVL_I(inode)->version++;
}
u64 ovl_dentry_version_get(struct dentry *dentry)
@@ -382,6 +389,11 @@ void ovl_set_flag(unsigned long flag, struct inode *inode)
set_bit(flag, &OVL_I(inode)->flags);
}
+void ovl_clear_flag(unsigned long flag, struct inode *inode)
+{
+ clear_bit(flag, &OVL_I(inode)->flags);
+}
+
bool ovl_test_flag(unsigned long flag, struct inode *inode)
{
return test_bit(flag, &OVL_I(inode)->flags);
diff --git a/fs/xattr.c b/fs/xattr.c
index 7b03df6b8be2..4424f7fecf14 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -23,6 +23,7 @@
#include <linux/posix_acl_xattr.h>
#include <linux/uaccess.h>
+#include "internal.h"
static const char *
strcmp_prefix(const char *a, const char *a_prefix)
@@ -502,10 +503,10 @@ SYSCALL_DEFINE5(fsetxattr, int, fd, const char __user *, name,
if (!f.file)
return error;
audit_file(f.file);
- error = mnt_want_write_file(f.file);
+ error = mnt_want_write_file_path(f.file);
if (!error) {
error = setxattr(f.file->f_path.dentry, name, value, size, flags);
- mnt_drop_write_file(f.file);
+ mnt_drop_write_file_path(f.file);
}
fdput(f);
return error;
@@ -734,10 +735,10 @@ SYSCALL_DEFINE2(fremovexattr, int, fd, const char __user *, name)
if (!f.file)
return error;
audit_file(f.file);
- error = mnt_want_write_file(f.file);
+ error = mnt_want_write_file_path(f.file);
if (!error) {
error = removexattr(f.file->f_path.dentry, name);
- mnt_drop_write_file(f.file);
+ mnt_drop_write_file_path(f.file);
}
fdput(f);
return error;