Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b0aa7606 authored by Maxim Patlasov's avatar Maxim Patlasov Committed by Miklos Szeredi
Browse files

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: default avatarMaxim Patlasov <MPatlasov@parallels.com>
Signed-off-by: default avatarMiklos Szeredi <mszeredi@suse.cz>
parent 8373200b
Loading
Loading
Loading
Loading
+91 −17
Original line number Diff line number Diff line
@@ -842,8 +842,11 @@ static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr,
	struct fuse_conn *fc = get_fuse_conn(inode);

	/* 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->mtime = inode->i_mtime.tv_sec;
		attr->mtimensec = inode->i_mtime.tv_nsec;
	}

	stat->dev = inode->i_sb->s_dev;
	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);
}

static bool update_mtime(unsigned ivalid)
static bool update_mtime(unsigned ivalid, bool trust_local_mtime)
{
	/* Always update if mtime is explicitly set  */
	if (ivalid & ATTR_MTIME_SET)
		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 ((ivalid & ATTR_SIZE) && (ivalid & (ATTR_OPEN | ATTR_FILE)))
		return false;
@@ -1496,7 +1503,8 @@ static bool update_mtime(unsigned ivalid)
	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;

@@ -1515,11 +1523,11 @@ static void iattr_to_fattr(struct iattr *iattr, struct fuse_setattr_in *arg)
		if (!(ivalid & ATTR_ATIME_SET))
			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->mtime = iattr->ia_mtime.tv_sec;
		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;
	}
}
@@ -1568,6 +1576,63 @@ void fuse_release_nowrite(struct inode *inode)
	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.
 *
@@ -1588,6 +1653,7 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr,
	bool is_wb = fc->writeback_cache;
	loff_t oldsize;
	int err;
	bool trust_local_mtime = is_wb && S_ISREG(inode->i_mode);

	if (!(fc->flags & FUSE_DEFAULT_PERMISSIONS))
		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(&outarg, 0, sizeof(outarg));
	iattr_to_fattr(attr, &inarg);
	iattr_to_fattr(attr, &inarg, trust_local_mtime);
	if (file) {
		struct fuse_file *ff = file->private_data;
		inarg.valid |= FATTR_FH;
@@ -1627,17 +1693,7 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr,
		inarg.valid |= FATTR_LOCKOWNER;
		inarg.lock_owner = fuse_lock_owner_id(fc, current->files);
	}
	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);
	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_setattr_fill(fc, req, inode, &inarg, &outarg);
	fuse_request_send(fc, req);
	err = req->out.h.error;
	fuse_put_request(fc, req);
@@ -1654,6 +1710,12 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr,
	}

	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,
				      attr_timeout(&outarg));
	oldsize = inode->i_size;
@@ -1884,6 +1946,17 @@ static int fuse_removexattr(struct dentry *entry, const char *name)
	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 = {
	.lookup		= fuse_lookup,
	.mkdir		= fuse_mkdir,
@@ -1923,6 +1996,7 @@ static const struct inode_operations fuse_common_inode_operations = {
	.getxattr	= fuse_getxattr,
	.listxattr	= fuse_listxattr,
	.removexattr	= fuse_removexattr,
	.update_time	= fuse_update_time,
};

static const struct inode_operations fuse_symlink_inode_operations = {
+26 −4
Original line number Diff line number Diff line
@@ -308,6 +308,9 @@ static int fuse_open(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);

	/* 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);

	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);
	if (IS_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;
}

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_inode *fi = get_fuse_inode(inode);
	bool ret = false;

	spin_lock(&fc->lock);
	fi->attr_version = ++fc->attr_version;
	if (pos > inode->i_size)
	if (pos > inode->i_size) {
		i_size_write(inode, pos);
		ret = true;
	}
	spin_unlock(&fc->lock);

	return ret;
}

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;

	/* we could have extended the file */
	if (!(mode & FALLOC_FL_KEEP_SIZE))
		fuse_write_update_size(inode, offset + length);
	if (!(mode & FALLOC_FL_KEEP_SIZE)) {
		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)
		truncate_pagecache_range(inode, offset, offset + length - 1);
+5 −1
Original line number Diff line number Diff line
@@ -119,6 +119,8 @@ enum {
	FUSE_I_INIT_RDPLUS,
	/** An operation changing file size is in progress  */
	FUSE_I_SIZE_UNSTABLE,
	/** i_mtime has been updated locally; a flush to userspace needed */
	FUSE_I_MTIME_DIRTY,
};

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);
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,
		    struct file *file);
+10 −3
Original line number Diff line number Diff line
@@ -170,8 +170,11 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
	inode->i_blocks  = attr->blocks;
	inode->i_atime.tv_sec   = attr->atime;
	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_nsec  = attr->mtimensec;
	}
	inode->i_ctime.tv_sec   = attr->ctime;
	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_size = attr->size;
	inode->i_mtime.tv_sec  = attr->mtime;
	inode->i_mtime.tv_nsec = attr->mtimensec;
	if (S_ISREG(inode->i_mode)) {
		fuse_init_common(inode);
		fuse_init_file_inode(inode);
@@ -296,7 +301,9 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
		return NULL;

	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_data.backing_dev_info = &fc->bdi;
		fuse_init_inode(inode, attr);