From 7736e8cc51bbfdbd538c1870c314eb3483fe04ed Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Wed, 23 Apr 2014 18:14:42 +0200 Subject: [PATCH 01/16] fuse: add __exit to fuse_ctl_cleanup fuse_ctl_cleanup is only called by __exit fuse_exit Signed-off-by: Fabian Frederick Signed-off-by: Miklos Szeredi --- fs/fuse/control.c | 2 +- fs/fuse/fuse_i.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/fuse/control.c b/fs/fuse/control.c index a0b0855d00a9..205e0d5d5307 100644 --- a/fs/fuse/control.c +++ b/fs/fuse/control.c @@ -348,7 +348,7 @@ int __init fuse_ctl_init(void) return register_filesystem(&fuse_ctl_fs_type); } -void fuse_ctl_cleanup(void) +void __exit fuse_ctl_cleanup(void) { unregister_filesystem(&fuse_ctl_fs_type); } diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index a257ed8ebee6..adfa2d505c1a 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -725,7 +725,7 @@ int fuse_dev_init(void); void fuse_dev_cleanup(void); int fuse_ctl_init(void); -void fuse_ctl_cleanup(void); +void __exit fuse_ctl_cleanup(void); /** * Allocate a request From 4adb83029de8ef5144a14dbb5c21de0f156c1a03 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 28 Apr 2014 14:19:21 +0200 Subject: [PATCH 02/16] fuse: check fallocate mode Don't allow new fallocate modes until we figure out what (if anything) that takes. Signed-off-by: Miklos Szeredi --- fs/fuse/file.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 13f8bdec5110..982288a1c6ab 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -2972,6 +2972,9 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset, bool lock_inode = !(mode & FALLOC_FL_KEEP_SIZE) || (mode & FALLOC_FL_PUNCH_HOLE); + if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE)) + return -EOPNOTSUPP; + if (fc->no_fallocate) return -EOPNOTSUPP; From aeb4eb6b55261f44d3705f0a3b9e4906bfa5e416 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 28 Apr 2014 14:19:21 +0200 Subject: [PATCH 03/16] fuse: fix mtime update error in fsync Bad case of shadowing. Signed-off-by: Miklos Szeredi --- fs/fuse/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 982288a1c6ab..7ae1e6972caa 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -501,7 +501,7 @@ 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); + err = fuse_flush_mtime(file, false); if (err) goto out; } From d31433c8b06d44e27f7637574137dc4b5e6fd1d1 Mon Sep 17 00:00:00 2001 From: Maxim Patlasov Date: Mon, 28 Apr 2014 14:19:21 +0200 Subject: [PATCH 04/16] fuse: do not use uninitialized i_mode When inode is in I_NEW state, inode->i_mode is not initialized yet. Do not use it before fuse_init_inode() is called. Signed-off-by: Maxim Patlasov Signed-off-by: Miklos Szeredi --- fs/fuse/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 8d611696fcad..299e553fcdfd 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -303,7 +303,7 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid, if ((inode->i_state & I_NEW)) { inode->i_flags |= S_NOATIME; - if (!fc->writeback_cache || !S_ISREG(inode->i_mode)) + if (!fc->writeback_cache || !S_ISREG(attr->mode)) inode->i_flags |= S_NOCMTIME; inode->i_generation = generation; inode->i_data.backing_dev_info = &fc->bdi; From 009dd694e82098a703b8b5c8dd9f54c131dbb9b3 Mon Sep 17 00:00:00 2001 From: Maxim Patlasov Date: Mon, 28 Apr 2014 14:19:22 +0200 Subject: [PATCH 05/16] fuse: update mtime on truncate(2) Handling truncate(2), VFS doesn't set ATTR_MTIME bit in iattr structure; only ATTR_SIZE bit is set. In-kernel fuse must handle the case by setting mtime fields of struct fuse_setattr_in to "now" and set FATTR_MTIME bit even though ATTR_MTIME was not set. Signed-off-by: Maxim Patlasov Signed-off-by: Miklos Szeredi --- fs/fuse/dir.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 5b4e035b364c..5e361b122526 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1678,6 +1678,8 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr, if (is_truncate) { fuse_set_nowrite(inode); set_bit(FUSE_I_SIZE_UNSTABLE, &fi->state); + if (trust_local_mtime && attr->ia_size != inode->i_size) + attr->ia_valid |= ATTR_MTIME; } memset(&inarg, 0, sizeof(inarg)); From 75caeecdf9c7151af5f7d972e2dabbff1bef30a7 Mon Sep 17 00:00:00 2001 From: Maxim Patlasov Date: Mon, 28 Apr 2014 14:19:22 +0200 Subject: [PATCH 06/16] fuse: update mtime on open(O_TRUNC) in atomic_o_trunc mode In case of fc->atomic_o_trunc is set, fuse does nothing in fuse_do_setattr() while handling open(O_TRUNC). Hence, i_mtime must be updated explicitly in fuse_finish_open(). The patch also adds extra locking encompassing open(O_TRUNC) operation to avoid races between the truncation and updating i_mtime. Signed-off-by: Maxim Patlasov Signed-off-by: Miklos Szeredi --- fs/fuse/file.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 7ae1e6972caa..e68d8c3f063a 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -223,6 +223,8 @@ void fuse_finish_open(struct inode *inode, struct file *file) i_size_write(inode, 0); spin_unlock(&fc->lock); fuse_invalidate_attr(inode); + if (fc->writeback_cache) + file_update_time(file); } if ((file->f_mode & FMODE_WRITE) && fc->writeback_cache) fuse_link_write_file(file); @@ -232,18 +234,26 @@ int fuse_open_common(struct inode *inode, struct file *file, bool isdir) { struct fuse_conn *fc = get_fuse_conn(inode); int err; + bool lock_inode = (file->f_flags & O_TRUNC) && + fc->atomic_o_trunc && + fc->writeback_cache; err = generic_file_open(inode, file); if (err) return err; + if (lock_inode) + mutex_lock(&inode->i_mutex); + err = fuse_do_open(fc, get_node_id(inode), file, isdir); - if (err) - return err; - fuse_finish_open(inode, file); + if (!err) + fuse_finish_open(inode, file); - return 0; + if (lock_inode) + mutex_unlock(&inode->i_mutex); + + return err; } static void fuse_prepare_release(struct fuse_file *ff, int flags, int opcode) From 93d2269d2ffb871fdfc5555cb5d4a7c0fc56e7fe Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 28 Apr 2014 14:19:22 +0200 Subject: [PATCH 07/16] fuse: fuse: fallocate: use file_update_time() in preparation for getting rid of FUSE_I_MTIME_DIRTY. Signed-off-by: Miklos Szeredi --- fs/fuse/file.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index e68d8c3f063a..3391f6a840bd 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3030,12 +3030,8 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset, 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 (changed && fc->writeback_cache) + file_update_time(file); } if (mode & FALLOC_FL_PUNCH_HOLE) From 22401e7b7a686bff02549cd648ba6f73f8dba868 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 28 Apr 2014 14:19:23 +0200 Subject: [PATCH 08/16] fuse: clean up fsync Don't need to start I/O twice (once without i_mutex and one within). Also make sure that even if the userspace filesystem doesn't support FSYNC we do all the steps other than sending the message. Signed-off-by: Miklos Szeredi --- fs/fuse/file.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 3391f6a840bd..65586a567d0d 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -490,13 +490,6 @@ int fuse_fsync_common(struct file *file, loff_t start, loff_t end, if (is_bad_inode(inode)) return -EIO; - err = filemap_write_and_wait_range(inode->i_mapping, start, end); - if (err) - return err; - - if ((!isdir && fc->no_fsync) || (isdir && fc->no_fsyncdir)) - return 0; - mutex_lock(&inode->i_mutex); /* @@ -504,7 +497,7 @@ int fuse_fsync_common(struct file *file, loff_t start, loff_t end, * wait for all outstanding writes, before sending the FSYNC * request. */ - err = write_inode_now(inode, 0); + err = filemap_write_and_wait_range(inode->i_mapping, start, end); if (err) goto out; @@ -515,6 +508,8 @@ int fuse_fsync_common(struct file *file, loff_t start, loff_t end, if (err) goto out; } + if ((!isdir && fc->no_fsync) || (isdir && fc->no_fsyncdir)) + goto out; req = fuse_get_req_nopages(fc); if (IS_ERR(req)) { From 1e18bda86e2dcc4ecb176213ee34649c93ad1396 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 28 Apr 2014 14:19:23 +0200 Subject: [PATCH 09/16] fuse: add .write_inode ...and flush mtime from this. This allows us to use the kernel infrastructure for writing out dirty metadata (mtime at this point, but ctime in the next patches and also maybe atime). Signed-off-by: Miklos Szeredi --- fs/fuse/dir.c | 28 +++++++++++----------------- fs/fuse/file.c | 44 +++++++++++++++++++++++++++++++------------- fs/fuse/fuse_i.h | 5 ++--- fs/fuse/inode.c | 1 + 4 files changed, 45 insertions(+), 33 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 5e361b122526..8c233834591f 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1597,23 +1597,17 @@ static void fuse_setattr_fill(struct fuse_conn *fc, struct fuse_req *req, /* * Flush inode->i_mtime to the server */ -int fuse_flush_mtime(struct file *file, bool nofail) +int fuse_flush_mtime(struct inode *inode, struct fuse_file *ff) { - 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_req *req; 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); - } + req = fuse_get_req_nopages(fc); + if (IS_ERR(req)) + return PTR_ERR(req); memset(&inarg, 0, sizeof(inarg)); memset(&outarg, 0, sizeof(outarg)); @@ -1621,15 +1615,15 @@ int fuse_flush_mtime(struct file *file, bool nofail) inarg.valid |= FATTR_MTIME; inarg.mtime = inode->i_mtime.tv_sec; inarg.mtimensec = inode->i_mtime.tv_nsec; - + if (ff) { + inarg.valid |= FATTR_FH; + inarg.fh = ff->fh; + } 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; } @@ -1715,7 +1709,7 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr, /* 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); + /* FIXME: clear I_DIRTY_SYNC? */ } fuse_change_attributes_common(inode, &outarg.attr, @@ -1953,7 +1947,7 @@ static int fuse_update_time(struct inode *inode, struct timespec *now, { if (flags & S_MTIME) { inode->i_mtime = *now; - set_bit(FUSE_I_MTIME_DIRTY, &get_fuse_inode(inode)->state); + mark_inode_dirty_sync(inode); BUG_ON(!S_ISREG(inode->i_mode)); } return 0; diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 65586a567d0d..d228c3962ffd 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -324,10 +324,7 @@ static int fuse_release(struct inode *inode, struct file *file) /* see fuse_vma_close() for !writeback_cache case */ if (fc->writeback_cache) - filemap_write_and_wait(file->f_mapping); - - if (test_bit(FUSE_I_MTIME_DIRTY, &get_fuse_inode(inode)->state)) - fuse_flush_mtime(file, true); + write_inode_now(inode, 1); fuse_release_common(file, FUSE_RELEASE); @@ -449,7 +446,7 @@ static int fuse_flush(struct file *file, fl_owner_t id) if (fc->no_flush) return 0; - err = filemap_write_and_wait(file->f_mapping); + err = write_inode_now(inode, 1); if (err) return err; @@ -502,12 +499,10 @@ int fuse_fsync_common(struct file *file, loff_t start, loff_t end, goto out; fuse_sync_writes(inode); + err = sync_inode_metadata(inode, 1); + if (err) + goto out; - if (test_bit(FUSE_I_MTIME_DIRTY, &get_fuse_inode(inode)->state)) { - err = fuse_flush_mtime(file, false); - if (err) - goto out; - } if ((!isdir && fc->no_fsync) || (isdir && fc->no_fsyncdir)) goto out; @@ -1664,13 +1659,13 @@ static void fuse_writepage_end(struct fuse_conn *fc, struct fuse_req *req) fuse_writepage_free(fc, req); } -static struct fuse_file *fuse_write_file_get(struct fuse_conn *fc, - struct fuse_inode *fi) +static struct fuse_file *__fuse_write_file_get(struct fuse_conn *fc, + struct fuse_inode *fi) { struct fuse_file *ff = NULL; spin_lock(&fc->lock); - if (!WARN_ON(list_empty(&fi->write_files))) { + if (!list_empty(&fi->write_files)) { ff = list_entry(fi->write_files.next, struct fuse_file, write_entry); fuse_file_get(ff); @@ -1680,6 +1675,29 @@ static struct fuse_file *fuse_write_file_get(struct fuse_conn *fc, return ff; } +static struct fuse_file *fuse_write_file_get(struct fuse_conn *fc, + struct fuse_inode *fi) +{ + struct fuse_file *ff = __fuse_write_file_get(fc, fi); + WARN_ON(!ff); + return ff; +} + +int fuse_write_inode(struct inode *inode, struct writeback_control *wbc) +{ + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_inode *fi = get_fuse_inode(inode); + struct fuse_file *ff; + int err; + + ff = __fuse_write_file_get(fc, fi); + err = fuse_flush_mtime(inode, ff); + if (ff) + fuse_file_put(ff, 0); + + return err; +} + static int fuse_writepage_locked(struct page *page) { struct address_space *mapping = page->mapping; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index adfa2d505c1a..d2f10054b9a1 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -119,8 +119,6 @@ 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; @@ -891,7 +889,8 @@ int fuse_dev_release(struct inode *inode, struct file *file); bool fuse_write_update_size(struct inode *inode, loff_t pos); -int fuse_flush_mtime(struct file *file, bool nofail); +int fuse_flush_mtime(struct inode *inode, struct fuse_file *ff); +int fuse_write_inode(struct inode *inode, struct writeback_control *wbc); int fuse_do_setattr(struct inode *inode, struct iattr *attr, struct file *file); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 299e553fcdfd..5997e4940512 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -788,6 +788,7 @@ static const struct super_operations fuse_super_operations = { .alloc_inode = fuse_alloc_inode, .destroy_inode = fuse_destroy_inode, .evict_inode = fuse_evict_inode, + .write_inode = fuse_write_inode, .drop_inode = generic_delete_inode, .remount_fs = fuse_remount_fs, .put_super = fuse_put_super, From e27c9d3877a0d0479711a55f5cdd7ee91442da53 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 28 Apr 2014 14:19:23 +0200 Subject: [PATCH 10/16] fuse: fuse: add time_gran to INIT_OUT Allow userspace fs to specify time granularity. This is needed because with writeback_cache mode the kernel is responsible for generating mtime and ctime, but if the underlying filesystem doesn't support nanosecond granularity then the cache will contain a different value from the one stored on the filesystem resulting in a change of times after a cache flush. Make the default granularity 1s. Signed-off-by: Miklos Szeredi --- fs/fuse/inode.c | 5 +++++ include/uapi/linux/fuse.h | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 5997e4940512..560eafcdd6a7 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -891,6 +891,11 @@ static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req) fc->async_dio = 1; if (arg->flags & FUSE_WRITEBACK_CACHE) fc->writeback_cache = 1; + if (arg->time_gran && arg->time_gran <= 1000000000) + fc->sb->s_time_gran = arg->time_gran; + else + fc->sb->s_time_gran = 1000000000; + } else { ra_pages = fc->max_read / PAGE_CACHE_SIZE; fc->no_lock = 1; diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index cf4750e1bb49..d1b4e2ca9672 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -96,6 +96,8 @@ * * 7.23 * - add FUSE_WRITEBACK_CACHE + * - add time_gran to fuse_init_out + * - add reserved space to fuse_init_out */ #ifndef _LINUX_FUSE_H @@ -559,6 +561,9 @@ struct fuse_init_in { uint32_t flags; }; +#define FUSE_COMPAT_INIT_OUT_SIZE 8 +#define FUSE_COMPAT_22_INIT_OUT_SIZE 24 + struct fuse_init_out { uint32_t major; uint32_t minor; @@ -567,6 +572,8 @@ struct fuse_init_out { uint16_t max_background; uint16_t congestion_threshold; uint32_t max_write; + uint32_t time_gran; + uint32_t unused[9]; }; #define CUSE_INIT_INFO_MAX 4096 From ab9e13f7c771b511d8f71666e83cb27bcc635b98 Mon Sep 17 00:00:00 2001 From: Maxim Patlasov Date: Mon, 28 Apr 2014 14:19:24 +0200 Subject: [PATCH 11/16] fuse: allow ctime flushing to userspace The patch extends fuse_setattr_in, and extends the flush procedure (fuse_flush_times()) called on ->write_inode() to send the ctime as well as mtime. Signed-off-by: Maxim Patlasov Signed-off-by: Miklos Szeredi --- fs/fuse/dir.c | 9 +++++++-- fs/fuse/file.c | 2 +- fs/fuse/fuse_i.h | 2 +- include/uapi/linux/fuse.h | 7 +++++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 8c233834591f..e6ba579e2937 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1597,7 +1597,7 @@ static void fuse_setattr_fill(struct fuse_conn *fc, struct fuse_req *req, /* * Flush inode->i_mtime to the server */ -int fuse_flush_mtime(struct inode *inode, struct fuse_file *ff) +int fuse_flush_times(struct inode *inode, struct fuse_file *ff) { struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_req *req; @@ -1612,9 +1612,14 @@ int fuse_flush_mtime(struct inode *inode, struct fuse_file *ff) memset(&inarg, 0, sizeof(inarg)); memset(&outarg, 0, sizeof(outarg)); - inarg.valid |= FATTR_MTIME; + inarg.valid = FATTR_MTIME; inarg.mtime = inode->i_mtime.tv_sec; inarg.mtimensec = inode->i_mtime.tv_nsec; + if (fc->minor >= 23) { + inarg.valid |= FATTR_CTIME; + inarg.ctime = inode->i_ctime.tv_sec; + inarg.ctimensec = inode->i_ctime.tv_nsec; + } if (ff) { inarg.valid |= FATTR_FH; inarg.fh = ff->fh; diff --git a/fs/fuse/file.c b/fs/fuse/file.c index d228c3962ffd..96d513e01a5d 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1691,7 +1691,7 @@ int fuse_write_inode(struct inode *inode, struct writeback_control *wbc) int err; ff = __fuse_write_file_get(fc, fi); - err = fuse_flush_mtime(inode, ff); + err = fuse_flush_times(inode, ff); if (ff) fuse_file_put(ff, 0); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index d2f10054b9a1..40677e33504f 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -889,7 +889,7 @@ int fuse_dev_release(struct inode *inode, struct file *file); bool fuse_write_update_size(struct inode *inode, loff_t pos); -int fuse_flush_mtime(struct inode *inode, struct fuse_file *ff); +int fuse_flush_times(struct inode *inode, struct fuse_file *ff); int fuse_write_inode(struct inode *inode, struct writeback_control *wbc); int fuse_do_setattr(struct inode *inode, struct iattr *attr, diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index d1b4e2ca9672..e86a21acef75 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -98,6 +98,8 @@ * - add FUSE_WRITEBACK_CACHE * - add time_gran to fuse_init_out * - add reserved space to fuse_init_out + * - add FATTR_CTIME + * - add ctime and ctimensec to fuse_setattr_in */ #ifndef _LINUX_FUSE_H @@ -193,6 +195,7 @@ struct fuse_file_lock { #define FATTR_ATIME_NOW (1 << 7) #define FATTR_MTIME_NOW (1 << 8) #define FATTR_LOCKOWNER (1 << 9) +#define FATTR_CTIME (1 << 10) /** * Flags returned by the OPEN request @@ -440,10 +443,10 @@ struct fuse_setattr_in { uint64_t lock_owner; uint64_t atime; uint64_t mtime; - uint64_t unused2; + uint64_t ctime; uint32_t atimensec; uint32_t mtimensec; - uint32_t unused3; + uint32_t ctimensec; uint32_t mode; uint32_t unused4; uint32_t uid; From 8b47e73e91c064cd75e8bf458ce738e1bfe2e700 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 28 Apr 2014 14:19:24 +0200 Subject: [PATCH 12/16] fuse: remove .update_time This implements updating ctime as well as mtime on file_update_time(). Signed-off-by: Miklos Szeredi --- fs/fuse/dir.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index e6ba579e2937..65357bd1d17b 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1947,17 +1947,6 @@ 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; - mark_inode_dirty_sync(inode); - BUG_ON(!S_ISREG(inode->i_mode)); - } - return 0; -} - static const struct inode_operations fuse_dir_inode_operations = { .lookup = fuse_lookup, .mkdir = fuse_mkdir, @@ -1997,7 +1986,6 @@ 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 = { From 31f3267b4ba16b12fb9dd3b1953ea0f221cc2ab4 Mon Sep 17 00:00:00 2001 From: Maxim Patlasov Date: Mon, 28 Apr 2014 14:19:24 +0200 Subject: [PATCH 13/16] fuse: trust kernel i_ctime only Let the kernel maintain i_ctime locally: update i_ctime explicitly on truncate, fallocate, open(O_TRUNC), setxattr, removexattr, link, rename, unlink. The inode flag I_DIRTY_SYNC serves as indication that local i_ctime should be flushed to the server eventually. The patch sets the flag and updates i_ctime in course of operations listed above. Signed-off-by: Maxim Patlasov Signed-off-by: Miklos Szeredi --- fs/fuse/dir.c | 22 ++++++++++++++++++++-- fs/fuse/inode.c | 6 ++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 65357bd1d17b..f62ab8eedb5f 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -679,6 +679,14 @@ static int fuse_symlink(struct inode *dir, struct dentry *entry, return create_new_entry(fc, req, dir, entry, S_IFLNK); } +static inline void fuse_update_ctime(struct inode *inode) +{ + if (!IS_NOCMTIME(inode)) { + inode->i_ctime = current_fs_time(inode->i_sb); + mark_inode_dirty_sync(inode); + } +} + static int fuse_unlink(struct inode *dir, struct dentry *entry) { int err; @@ -713,6 +721,7 @@ static int fuse_unlink(struct inode *dir, struct dentry *entry) fuse_invalidate_attr(inode); fuse_invalidate_attr(dir); fuse_invalidate_entry_cache(entry); + fuse_update_ctime(inode); } else if (err == -EINTR) fuse_invalidate_entry(entry); return err; @@ -771,6 +780,7 @@ static int fuse_rename(struct inode *olddir, struct dentry *oldent, if (!err) { /* ctime changes */ fuse_invalidate_attr(oldent->d_inode); + fuse_update_ctime(oldent->d_inode); fuse_invalidate_attr(olddir); if (olddir != newdir) @@ -780,6 +790,7 @@ static int fuse_rename(struct inode *olddir, struct dentry *oldent, if (newent->d_inode) { fuse_invalidate_attr(newent->d_inode); fuse_invalidate_entry_cache(newent); + fuse_update_ctime(newent->d_inode); } } else if (err == -EINTR) { /* If request was interrupted, DEITY only knows if the @@ -829,6 +840,7 @@ static int fuse_link(struct dentry *entry, struct inode *newdir, inc_nlink(inode); spin_unlock(&fc->lock); fuse_invalidate_attr(inode); + fuse_update_ctime(inode); } else if (err == -EINTR) { fuse_invalidate_attr(inode); } @@ -846,6 +858,8 @@ static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr, attr->size = i_size_read(inode); attr->mtime = inode->i_mtime.tv_sec; attr->mtimensec = inode->i_mtime.tv_nsec; + attr->ctime = inode->i_ctime.tv_sec; + attr->ctimensec = inode->i_ctime.tv_nsec; } stat->dev = inode->i_sb->s_dev; @@ -1811,8 +1825,10 @@ static int fuse_setxattr(struct dentry *entry, const char *name, fc->no_setxattr = 1; err = -EOPNOTSUPP; } - if (!err) + if (!err) { fuse_invalidate_attr(inode); + fuse_update_ctime(inode); + } return err; } @@ -1942,8 +1958,10 @@ static int fuse_removexattr(struct dentry *entry, const char *name) fc->no_removexattr = 1; err = -EOPNOTSUPP; } - if (!err) + if (!err) { fuse_invalidate_attr(inode); + fuse_update_ctime(inode); + } return err; } diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 560eafcdd6a7..e9ecb1878109 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -175,9 +175,9 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr, 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; } - inode->i_ctime.tv_sec = attr->ctime; - inode->i_ctime.tv_nsec = attr->ctimensec; if (attr->blksize != 0) inode->i_blkbits = ilog2(attr->blksize); @@ -256,6 +256,8 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr) inode->i_size = attr->size; 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; if (S_ISREG(inode->i_mode)) { fuse_init_common(inode); fuse_init_file_inode(inode); From 3ad22c62dd23ad26c8737c300f455de60ba01f40 Mon Sep 17 00:00:00 2001 From: Maxim Patlasov Date: Mon, 28 Apr 2014 14:19:25 +0200 Subject: [PATCH 14/16] fuse: clear FUSE_I_CTIME_DIRTY flag on setattr The patch addresses two use-cases when the flag may be safely cleared: 1. fuse_do_setattr() is called with ATTR_CTIME flag set in attr->ia_valid. In this case attr->ia_ctime bears actual value. In-kernel fuse must send it to the userspace server and then assign the value to inode->i_ctime. 2. fuse_do_setattr() is called with ATTR_SIZE flag set in attr->ia_valid, whereas ATTR_CTIME is not set (truncate(2)). In this case in-kernel fuse must sent "now" to the userspace server and then assign the value to inode->i_ctime. In both cases we could clear I_DIRTY_SYNC, but that needs more thought. Signed-off-by: Maxim Patlasov Signed-off-by: Miklos Szeredi --- fs/fuse/dir.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index f62ab8eedb5f..843dcf1222eb 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1518,7 +1518,7 @@ static bool update_mtime(unsigned ivalid, bool trust_local_mtime) } static void iattr_to_fattr(struct iattr *iattr, struct fuse_setattr_in *arg, - bool trust_local_mtime) + bool trust_local_cmtime) { unsigned ivalid = iattr->ia_valid; @@ -1537,13 +1537,18 @@ 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, trust_local_mtime)) { + if ((ivalid & ATTR_MTIME) && update_mtime(ivalid, trust_local_cmtime)) { arg->valid |= FATTR_MTIME; arg->mtime = iattr->ia_mtime.tv_sec; arg->mtimensec = iattr->ia_mtime.tv_nsec; - if (!(ivalid & ATTR_MTIME_SET) && !trust_local_mtime) + if (!(ivalid & ATTR_MTIME_SET) && !trust_local_cmtime) arg->valid |= FATTR_MTIME_NOW; } + if ((ivalid & ATTR_CTIME) && trust_local_cmtime) { + arg->valid |= FATTR_CTIME; + arg->ctime = iattr->ia_ctime.tv_sec; + arg->ctimensec = iattr->ia_ctime.tv_nsec; + } } /* @@ -1666,7 +1671,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); + bool trust_local_cmtime = is_wb && S_ISREG(inode->i_mode); if (!(fc->flags & FUSE_DEFAULT_PERMISSIONS)) attr->ia_valid |= ATTR_FORCE; @@ -1691,13 +1696,13 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr, if (is_truncate) { fuse_set_nowrite(inode); set_bit(FUSE_I_SIZE_UNSTABLE, &fi->state); - if (trust_local_mtime && attr->ia_size != inode->i_size) - attr->ia_valid |= ATTR_MTIME; + if (trust_local_cmtime && attr->ia_size != inode->i_size) + attr->ia_valid |= ATTR_MTIME | ATTR_CTIME; } memset(&inarg, 0, sizeof(inarg)); memset(&outarg, 0, sizeof(outarg)); - iattr_to_fattr(attr, &inarg, trust_local_mtime); + iattr_to_fattr(attr, &inarg, trust_local_cmtime); if (file) { struct fuse_file *ff = file->private_data; inarg.valid |= FATTR_FH; @@ -1726,8 +1731,11 @@ 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; + if (trust_local_cmtime) { + if (attr->ia_valid & ATTR_MTIME) + inode->i_mtime = attr->ia_mtime; + if (attr->ia_valid & ATTR_CTIME) + inode->i_ctime = attr->ia_ctime; /* FIXME: clear I_DIRTY_SYNC? */ } From 4ace1f85a7cdab5453c2e12029ff978dd4cd6155 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 28 Apr 2014 14:19:25 +0200 Subject: [PATCH 15/16] fuse: clear MS_I_VERSION Fuse doesn't support i_version (yet). Signed-off-by: Miklos Szeredi --- fs/fuse/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index e9ecb1878109..754dcf23de8a 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1004,7 +1004,7 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) if (sb->s_flags & MS_MANDLOCK) goto err; - sb->s_flags &= ~MS_NOSEC; + sb->s_flags &= ~(MS_NOSEC | MS_I_VERSION); if (!parse_fuse_opt((char *) data, &d, is_bdev)) goto err; From 1560c974dcd40a8d3f193283acd7cc6aee13dc13 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 28 Apr 2014 16:43:44 +0200 Subject: [PATCH 16/16] fuse: add renameat2 support Support RENAME_EXCHANGE and RENAME_NOREPLACE flags on the userspace ABI. Signed-off-by: Miklos Szeredi --- fs/fuse/dir.c | 55 +++++++++++++++++++++++++++++++++------ fs/fuse/fuse_i.h | 3 +++ include/uapi/linux/fuse.h | 8 ++++++ 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 843dcf1222eb..42198359fa1b 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -752,23 +752,26 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry) return err; } -static int fuse_rename(struct inode *olddir, struct dentry *oldent, - struct inode *newdir, struct dentry *newent) +static int fuse_rename_common(struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent, + unsigned int flags, int opcode, size_t argsize) { int err; - struct fuse_rename_in inarg; + struct fuse_rename2_in inarg; struct fuse_conn *fc = get_fuse_conn(olddir); - struct fuse_req *req = fuse_get_req_nopages(fc); + struct fuse_req *req; + req = fuse_get_req_nopages(fc); if (IS_ERR(req)) return PTR_ERR(req); - memset(&inarg, 0, sizeof(inarg)); + memset(&inarg, 0, argsize); inarg.newdir = get_node_id(newdir); - req->in.h.opcode = FUSE_RENAME; + inarg.flags = flags; + req->in.h.opcode = opcode; req->in.h.nodeid = get_node_id(olddir); req->in.numargs = 3; - req->in.args[0].size = sizeof(inarg); + req->in.args[0].size = argsize; req->in.args[0].value = &inarg; req->in.args[1].size = oldent->d_name.len + 1; req->in.args[1].value = oldent->d_name.name; @@ -782,12 +785,17 @@ static int fuse_rename(struct inode *olddir, struct dentry *oldent, fuse_invalidate_attr(oldent->d_inode); fuse_update_ctime(oldent->d_inode); + if (flags & RENAME_EXCHANGE) { + fuse_invalidate_attr(newent->d_inode); + fuse_update_ctime(newent->d_inode); + } + fuse_invalidate_attr(olddir); if (olddir != newdir) fuse_invalidate_attr(newdir); /* newent will end up negative */ - if (newent->d_inode) { + if (!(flags & RENAME_EXCHANGE) && newent->d_inode) { fuse_invalidate_attr(newent->d_inode); fuse_invalidate_entry_cache(newent); fuse_update_ctime(newent->d_inode); @@ -806,6 +814,36 @@ static int fuse_rename(struct inode *olddir, struct dentry *oldent, return err; } +static int fuse_rename(struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent) +{ + return fuse_rename_common(olddir, oldent, newdir, newent, 0, + FUSE_RENAME, sizeof(struct fuse_rename_in)); +} + +static int fuse_rename2(struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent, + unsigned int flags) +{ + struct fuse_conn *fc = get_fuse_conn(olddir); + int err; + + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE)) + return -EINVAL; + + if (fc->no_rename2 || fc->minor < 23) + return -EINVAL; + + err = fuse_rename_common(olddir, oldent, newdir, newent, flags, + FUSE_RENAME2, sizeof(struct fuse_rename2_in)); + if (err == -ENOSYS) { + fc->no_rename2 = 1; + err = -EINVAL; + } + return err; + +} + static int fuse_link(struct dentry *entry, struct inode *newdir, struct dentry *newent) { @@ -1980,6 +2018,7 @@ static const struct inode_operations fuse_dir_inode_operations = { .unlink = fuse_unlink, .rmdir = fuse_rmdir, .rename = fuse_rename, + .rename2 = fuse_rename2, .link = fuse_link, .setattr = fuse_setattr, .create = fuse_create, diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 40677e33504f..7aa5c75e0de1 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -542,6 +542,9 @@ struct fuse_conn { /** Is fallocate not implemented by fs? */ unsigned no_fallocate:1; + /** Is rename with flags implemented by fs? */ + unsigned no_rename2:1; + /** Use enhanced/automatic page cache invalidation. */ unsigned auto_inval_data:1; diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index e86a21acef75..40b5ca8a1b1f 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -100,6 +100,7 @@ * - add reserved space to fuse_init_out * - add FATTR_CTIME * - add ctime and ctimensec to fuse_setattr_in + * - add FUSE_RENAME2 request */ #ifndef _LINUX_FUSE_H @@ -353,6 +354,7 @@ enum fuse_opcode { FUSE_BATCH_FORGET = 42, FUSE_FALLOCATE = 43, FUSE_READDIRPLUS = 44, + FUSE_RENAME2 = 45, /* CUSE specific operations */ CUSE_INIT = 4096, @@ -431,6 +433,12 @@ struct fuse_rename_in { uint64_t newdir; }; +struct fuse_rename2_in { + uint64_t newdir; + uint32_t flags; + uint32_t padding; +}; + struct fuse_link_in { uint64_t oldnodeid; };