fuse: Trust kernel i_mtime only
Let the kernel maintain i_mtime locally: - clear S_NOCMTIME - implement i_op->update_time() - flush mtime on fsync and last close - update i_mtime explicitly on truncate and fallocate Fuse inode flag FUSE_I_MTIME_DIRTY serves as indication that local i_mtime should be flushed to the server eventually. Signed-off-by: Maxim Patlasov <MPatlasov@parallels.com> Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
This commit is contained in:
parent
8373200b12
commit
b0aa760652
4 changed files with 132 additions and 25 deletions
108
fs/fuse/dir.c
108
fs/fuse/dir.c
|
@ -842,8 +842,11 @@ static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr,
|
||||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||||
|
|
||||||
/* see the comment in fuse_change_attributes() */
|
/* see the comment in fuse_change_attributes() */
|
||||||
if (fc->writeback_cache && S_ISREG(inode->i_mode))
|
if (fc->writeback_cache && S_ISREG(inode->i_mode)) {
|
||||||
attr->size = i_size_read(inode);
|
attr->size = i_size_read(inode);
|
||||||
|
attr->mtime = inode->i_mtime.tv_sec;
|
||||||
|
attr->mtimensec = inode->i_mtime.tv_nsec;
|
||||||
|
}
|
||||||
|
|
||||||
stat->dev = inode->i_sb->s_dev;
|
stat->dev = inode->i_sb->s_dev;
|
||||||
stat->ino = attr->ino;
|
stat->ino = attr->ino;
|
||||||
|
@ -1482,12 +1485,16 @@ static long fuse_dir_compat_ioctl(struct file *file, unsigned int cmd,
|
||||||
FUSE_IOCTL_COMPAT | FUSE_IOCTL_DIR);
|
FUSE_IOCTL_COMPAT | FUSE_IOCTL_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool update_mtime(unsigned ivalid)
|
static bool update_mtime(unsigned ivalid, bool trust_local_mtime)
|
||||||
{
|
{
|
||||||
/* Always update if mtime is explicitly set */
|
/* Always update if mtime is explicitly set */
|
||||||
if (ivalid & ATTR_MTIME_SET)
|
if (ivalid & ATTR_MTIME_SET)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
/* Or if kernel i_mtime is the official one */
|
||||||
|
if (trust_local_mtime)
|
||||||
|
return true;
|
||||||
|
|
||||||
/* If it's an open(O_TRUNC) or an ftruncate(), don't update */
|
/* If it's an open(O_TRUNC) or an ftruncate(), don't update */
|
||||||
if ((ivalid & ATTR_SIZE) && (ivalid & (ATTR_OPEN | ATTR_FILE)))
|
if ((ivalid & ATTR_SIZE) && (ivalid & (ATTR_OPEN | ATTR_FILE)))
|
||||||
return false;
|
return false;
|
||||||
|
@ -1496,7 +1503,8 @@ static bool update_mtime(unsigned ivalid)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void iattr_to_fattr(struct iattr *iattr, struct fuse_setattr_in *arg)
|
static void iattr_to_fattr(struct iattr *iattr, struct fuse_setattr_in *arg,
|
||||||
|
bool trust_local_mtime)
|
||||||
{
|
{
|
||||||
unsigned ivalid = iattr->ia_valid;
|
unsigned ivalid = iattr->ia_valid;
|
||||||
|
|
||||||
|
@ -1515,11 +1523,11 @@ static void iattr_to_fattr(struct iattr *iattr, struct fuse_setattr_in *arg)
|
||||||
if (!(ivalid & ATTR_ATIME_SET))
|
if (!(ivalid & ATTR_ATIME_SET))
|
||||||
arg->valid |= FATTR_ATIME_NOW;
|
arg->valid |= FATTR_ATIME_NOW;
|
||||||
}
|
}
|
||||||
if ((ivalid & ATTR_MTIME) && update_mtime(ivalid)) {
|
if ((ivalid & ATTR_MTIME) && update_mtime(ivalid, trust_local_mtime)) {
|
||||||
arg->valid |= FATTR_MTIME;
|
arg->valid |= FATTR_MTIME;
|
||||||
arg->mtime = iattr->ia_mtime.tv_sec;
|
arg->mtime = iattr->ia_mtime.tv_sec;
|
||||||
arg->mtimensec = iattr->ia_mtime.tv_nsec;
|
arg->mtimensec = iattr->ia_mtime.tv_nsec;
|
||||||
if (!(ivalid & ATTR_MTIME_SET))
|
if (!(ivalid & ATTR_MTIME_SET) && !trust_local_mtime)
|
||||||
arg->valid |= FATTR_MTIME_NOW;
|
arg->valid |= FATTR_MTIME_NOW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1568,6 +1576,63 @@ void fuse_release_nowrite(struct inode *inode)
|
||||||
spin_unlock(&fc->lock);
|
spin_unlock(&fc->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void fuse_setattr_fill(struct fuse_conn *fc, struct fuse_req *req,
|
||||||
|
struct inode *inode,
|
||||||
|
struct fuse_setattr_in *inarg_p,
|
||||||
|
struct fuse_attr_out *outarg_p)
|
||||||
|
{
|
||||||
|
req->in.h.opcode = FUSE_SETATTR;
|
||||||
|
req->in.h.nodeid = get_node_id(inode);
|
||||||
|
req->in.numargs = 1;
|
||||||
|
req->in.args[0].size = sizeof(*inarg_p);
|
||||||
|
req->in.args[0].value = inarg_p;
|
||||||
|
req->out.numargs = 1;
|
||||||
|
if (fc->minor < 9)
|
||||||
|
req->out.args[0].size = FUSE_COMPAT_ATTR_OUT_SIZE;
|
||||||
|
else
|
||||||
|
req->out.args[0].size = sizeof(*outarg_p);
|
||||||
|
req->out.args[0].value = outarg_p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flush inode->i_mtime to the server
|
||||||
|
*/
|
||||||
|
int fuse_flush_mtime(struct file *file, bool nofail)
|
||||||
|
{
|
||||||
|
struct inode *inode = file->f_mapping->host;
|
||||||
|
struct fuse_inode *fi = get_fuse_inode(inode);
|
||||||
|
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||||
|
struct fuse_req *req = NULL;
|
||||||
|
struct fuse_setattr_in inarg;
|
||||||
|
struct fuse_attr_out outarg;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (nofail) {
|
||||||
|
req = fuse_get_req_nofail_nopages(fc, file);
|
||||||
|
} else {
|
||||||
|
req = fuse_get_req_nopages(fc);
|
||||||
|
if (IS_ERR(req))
|
||||||
|
return PTR_ERR(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&inarg, 0, sizeof(inarg));
|
||||||
|
memset(&outarg, 0, sizeof(outarg));
|
||||||
|
|
||||||
|
inarg.valid |= FATTR_MTIME;
|
||||||
|
inarg.mtime = inode->i_mtime.tv_sec;
|
||||||
|
inarg.mtimensec = inode->i_mtime.tv_nsec;
|
||||||
|
|
||||||
|
fuse_setattr_fill(fc, req, inode, &inarg, &outarg);
|
||||||
|
fuse_request_send(fc, req);
|
||||||
|
err = req->out.h.error;
|
||||||
|
fuse_put_request(fc, req);
|
||||||
|
|
||||||
|
if (!err)
|
||||||
|
clear_bit(FUSE_I_MTIME_DIRTY, &fi->state);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set attributes, and at the same time refresh them.
|
* Set attributes, and at the same time refresh them.
|
||||||
*
|
*
|
||||||
|
@ -1588,6 +1653,7 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr,
|
||||||
bool is_wb = fc->writeback_cache;
|
bool is_wb = fc->writeback_cache;
|
||||||
loff_t oldsize;
|
loff_t oldsize;
|
||||||
int err;
|
int err;
|
||||||
|
bool trust_local_mtime = is_wb && S_ISREG(inode->i_mode);
|
||||||
|
|
||||||
if (!(fc->flags & FUSE_DEFAULT_PERMISSIONS))
|
if (!(fc->flags & FUSE_DEFAULT_PERMISSIONS))
|
||||||
attr->ia_valid |= ATTR_FORCE;
|
attr->ia_valid |= ATTR_FORCE;
|
||||||
|
@ -1616,7 +1682,7 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr,
|
||||||
|
|
||||||
memset(&inarg, 0, sizeof(inarg));
|
memset(&inarg, 0, sizeof(inarg));
|
||||||
memset(&outarg, 0, sizeof(outarg));
|
memset(&outarg, 0, sizeof(outarg));
|
||||||
iattr_to_fattr(attr, &inarg);
|
iattr_to_fattr(attr, &inarg, trust_local_mtime);
|
||||||
if (file) {
|
if (file) {
|
||||||
struct fuse_file *ff = file->private_data;
|
struct fuse_file *ff = file->private_data;
|
||||||
inarg.valid |= FATTR_FH;
|
inarg.valid |= FATTR_FH;
|
||||||
|
@ -1627,17 +1693,7 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr,
|
||||||
inarg.valid |= FATTR_LOCKOWNER;
|
inarg.valid |= FATTR_LOCKOWNER;
|
||||||
inarg.lock_owner = fuse_lock_owner_id(fc, current->files);
|
inarg.lock_owner = fuse_lock_owner_id(fc, current->files);
|
||||||
}
|
}
|
||||||
req->in.h.opcode = FUSE_SETATTR;
|
fuse_setattr_fill(fc, req, inode, &inarg, &outarg);
|
||||||
req->in.h.nodeid = get_node_id(inode);
|
|
||||||
req->in.numargs = 1;
|
|
||||||
req->in.args[0].size = sizeof(inarg);
|
|
||||||
req->in.args[0].value = &inarg;
|
|
||||||
req->out.numargs = 1;
|
|
||||||
if (fc->minor < 9)
|
|
||||||
req->out.args[0].size = FUSE_COMPAT_ATTR_OUT_SIZE;
|
|
||||||
else
|
|
||||||
req->out.args[0].size = sizeof(outarg);
|
|
||||||
req->out.args[0].value = &outarg;
|
|
||||||
fuse_request_send(fc, req);
|
fuse_request_send(fc, req);
|
||||||
err = req->out.h.error;
|
err = req->out.h.error;
|
||||||
fuse_put_request(fc, req);
|
fuse_put_request(fc, req);
|
||||||
|
@ -1654,6 +1710,12 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr,
|
||||||
}
|
}
|
||||||
|
|
||||||
spin_lock(&fc->lock);
|
spin_lock(&fc->lock);
|
||||||
|
/* the kernel maintains i_mtime locally */
|
||||||
|
if (trust_local_mtime && (attr->ia_valid & ATTR_MTIME)) {
|
||||||
|
inode->i_mtime = attr->ia_mtime;
|
||||||
|
clear_bit(FUSE_I_MTIME_DIRTY, &fi->state);
|
||||||
|
}
|
||||||
|
|
||||||
fuse_change_attributes_common(inode, &outarg.attr,
|
fuse_change_attributes_common(inode, &outarg.attr,
|
||||||
attr_timeout(&outarg));
|
attr_timeout(&outarg));
|
||||||
oldsize = inode->i_size;
|
oldsize = inode->i_size;
|
||||||
|
@ -1884,6 +1946,17 @@ static int fuse_removexattr(struct dentry *entry, const char *name)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int fuse_update_time(struct inode *inode, struct timespec *now,
|
||||||
|
int flags)
|
||||||
|
{
|
||||||
|
if (flags & S_MTIME) {
|
||||||
|
inode->i_mtime = *now;
|
||||||
|
set_bit(FUSE_I_MTIME_DIRTY, &get_fuse_inode(inode)->state);
|
||||||
|
BUG_ON(!S_ISREG(inode->i_mode));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct inode_operations fuse_dir_inode_operations = {
|
static const struct inode_operations fuse_dir_inode_operations = {
|
||||||
.lookup = fuse_lookup,
|
.lookup = fuse_lookup,
|
||||||
.mkdir = fuse_mkdir,
|
.mkdir = fuse_mkdir,
|
||||||
|
@ -1923,6 +1996,7 @@ static const struct inode_operations fuse_common_inode_operations = {
|
||||||
.getxattr = fuse_getxattr,
|
.getxattr = fuse_getxattr,
|
||||||
.listxattr = fuse_listxattr,
|
.listxattr = fuse_listxattr,
|
||||||
.removexattr = fuse_removexattr,
|
.removexattr = fuse_removexattr,
|
||||||
|
.update_time = fuse_update_time,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct inode_operations fuse_symlink_inode_operations = {
|
static const struct inode_operations fuse_symlink_inode_operations = {
|
||||||
|
|
|
@ -308,6 +308,9 @@ static int fuse_open(struct inode *inode, struct file *file)
|
||||||
|
|
||||||
static int fuse_release(struct inode *inode, struct file *file)
|
static int fuse_release(struct inode *inode, struct file *file)
|
||||||
{
|
{
|
||||||
|
if (test_bit(FUSE_I_MTIME_DIRTY, &get_fuse_inode(inode)->state))
|
||||||
|
fuse_flush_mtime(file, true);
|
||||||
|
|
||||||
fuse_release_common(file, FUSE_RELEASE);
|
fuse_release_common(file, FUSE_RELEASE);
|
||||||
|
|
||||||
/* return value is ignored by VFS */
|
/* return value is ignored by VFS */
|
||||||
|
@ -475,6 +478,12 @@ int fuse_fsync_common(struct file *file, loff_t start, loff_t end,
|
||||||
|
|
||||||
fuse_sync_writes(inode);
|
fuse_sync_writes(inode);
|
||||||
|
|
||||||
|
if (test_bit(FUSE_I_MTIME_DIRTY, &get_fuse_inode(inode)->state)) {
|
||||||
|
int err = fuse_flush_mtime(file, false);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
req = fuse_get_req_nopages(fc);
|
req = fuse_get_req_nopages(fc);
|
||||||
if (IS_ERR(req)) {
|
if (IS_ERR(req)) {
|
||||||
err = PTR_ERR(req);
|
err = PTR_ERR(req);
|
||||||
|
@ -960,16 +969,21 @@ static size_t fuse_send_write(struct fuse_req *req, struct fuse_io_priv *io,
|
||||||
return req->misc.write.out.size;
|
return req->misc.write.out.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fuse_write_update_size(struct inode *inode, loff_t pos)
|
bool fuse_write_update_size(struct inode *inode, loff_t pos)
|
||||||
{
|
{
|
||||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||||
struct fuse_inode *fi = get_fuse_inode(inode);
|
struct fuse_inode *fi = get_fuse_inode(inode);
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
spin_lock(&fc->lock);
|
spin_lock(&fc->lock);
|
||||||
fi->attr_version = ++fc->attr_version;
|
fi->attr_version = ++fc->attr_version;
|
||||||
if (pos > inode->i_size)
|
if (pos > inode->i_size) {
|
||||||
i_size_write(inode, pos);
|
i_size_write(inode, pos);
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
spin_unlock(&fc->lock);
|
spin_unlock(&fc->lock);
|
||||||
|
|
||||||
|
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 file *file,
|
||||||
|
@ -2877,8 +2891,16 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset,
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
/* we could have extended the file */
|
/* we could have extended the file */
|
||||||
if (!(mode & FALLOC_FL_KEEP_SIZE))
|
if (!(mode & FALLOC_FL_KEEP_SIZE)) {
|
||||||
fuse_write_update_size(inode, offset + length);
|
bool changed = fuse_write_update_size(inode, offset + length);
|
||||||
|
|
||||||
|
if (changed && fc->writeback_cache) {
|
||||||
|
struct fuse_inode *fi = get_fuse_inode(inode);
|
||||||
|
|
||||||
|
inode->i_mtime = current_fs_time(inode->i_sb);
|
||||||
|
set_bit(FUSE_I_MTIME_DIRTY, &fi->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mode & FALLOC_FL_PUNCH_HOLE)
|
if (mode & FALLOC_FL_PUNCH_HOLE)
|
||||||
truncate_pagecache_range(inode, offset, offset + length - 1);
|
truncate_pagecache_range(inode, offset, offset + length - 1);
|
||||||
|
|
|
@ -119,6 +119,8 @@ enum {
|
||||||
FUSE_I_INIT_RDPLUS,
|
FUSE_I_INIT_RDPLUS,
|
||||||
/** An operation changing file size is in progress */
|
/** An operation changing file size is in progress */
|
||||||
FUSE_I_SIZE_UNSTABLE,
|
FUSE_I_SIZE_UNSTABLE,
|
||||||
|
/** i_mtime has been updated locally; a flush to userspace needed */
|
||||||
|
FUSE_I_MTIME_DIRTY,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct fuse_conn;
|
struct fuse_conn;
|
||||||
|
@ -876,7 +878,9 @@ long fuse_ioctl_common(struct file *file, unsigned int cmd,
|
||||||
unsigned fuse_file_poll(struct file *file, poll_table *wait);
|
unsigned fuse_file_poll(struct file *file, poll_table *wait);
|
||||||
int fuse_dev_release(struct inode *inode, struct file *file);
|
int fuse_dev_release(struct inode *inode, struct file *file);
|
||||||
|
|
||||||
void fuse_write_update_size(struct inode *inode, loff_t pos);
|
bool fuse_write_update_size(struct inode *inode, loff_t pos);
|
||||||
|
|
||||||
|
int fuse_flush_mtime(struct file *file, bool nofail);
|
||||||
|
|
||||||
int fuse_do_setattr(struct inode *inode, struct iattr *attr,
|
int fuse_do_setattr(struct inode *inode, struct iattr *attr,
|
||||||
struct file *file);
|
struct file *file);
|
||||||
|
|
|
@ -170,8 +170,11 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
|
||||||
inode->i_blocks = attr->blocks;
|
inode->i_blocks = attr->blocks;
|
||||||
inode->i_atime.tv_sec = attr->atime;
|
inode->i_atime.tv_sec = attr->atime;
|
||||||
inode->i_atime.tv_nsec = attr->atimensec;
|
inode->i_atime.tv_nsec = attr->atimensec;
|
||||||
|
/* mtime from server may be stale due to local buffered write */
|
||||||
|
if (!fc->writeback_cache || !S_ISREG(inode->i_mode)) {
|
||||||
inode->i_mtime.tv_sec = attr->mtime;
|
inode->i_mtime.tv_sec = attr->mtime;
|
||||||
inode->i_mtime.tv_nsec = attr->mtimensec;
|
inode->i_mtime.tv_nsec = attr->mtimensec;
|
||||||
|
}
|
||||||
inode->i_ctime.tv_sec = attr->ctime;
|
inode->i_ctime.tv_sec = attr->ctime;
|
||||||
inode->i_ctime.tv_nsec = attr->ctimensec;
|
inode->i_ctime.tv_nsec = attr->ctimensec;
|
||||||
|
|
||||||
|
@ -250,6 +253,8 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr)
|
||||||
{
|
{
|
||||||
inode->i_mode = attr->mode & S_IFMT;
|
inode->i_mode = attr->mode & S_IFMT;
|
||||||
inode->i_size = attr->size;
|
inode->i_size = attr->size;
|
||||||
|
inode->i_mtime.tv_sec = attr->mtime;
|
||||||
|
inode->i_mtime.tv_nsec = attr->mtimensec;
|
||||||
if (S_ISREG(inode->i_mode)) {
|
if (S_ISREG(inode->i_mode)) {
|
||||||
fuse_init_common(inode);
|
fuse_init_common(inode);
|
||||||
fuse_init_file_inode(inode);
|
fuse_init_file_inode(inode);
|
||||||
|
@ -296,7 +301,9 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if ((inode->i_state & I_NEW)) {
|
if ((inode->i_state & I_NEW)) {
|
||||||
inode->i_flags |= S_NOATIME|S_NOCMTIME;
|
inode->i_flags |= S_NOATIME;
|
||||||
|
if (!fc->writeback_cache || !S_ISREG(inode->i_mode))
|
||||||
|
inode->i_flags |= S_NOCMTIME;
|
||||||
inode->i_generation = generation;
|
inode->i_generation = generation;
|
||||||
inode->i_data.backing_dev_info = &fc->bdi;
|
inode->i_data.backing_dev_info = &fc->bdi;
|
||||||
fuse_init_inode(inode, attr);
|
fuse_init_inode(inode, attr);
|
||||||
|
|
Loading…
Add table
Reference in a new issue