fuse: make sure reclaim doesn't write the inode
authorMiklos Szeredi <mszeredi@redhat.com>
Fri, 22 Oct 2021 15:03:01 +0000 (17:03 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 17 Dec 2021 09:30:16 +0000 (10:30 +0100)
commit 5c791fe1e2a4f401f819065ea4fc0450849f1818 upstream.

In writeback cache mode mtime/ctime updates are cached, and flushed to the
server using the ->write_inode() callback.

Closing the file will result in a dirty inode being immediately written,
but in other cases the inode can remain dirty after all references are
dropped.  This result in the inode being written back from reclaim, which
can deadlock on a regular allocation while the request is being served.

The usual mechanisms (GFP_NOFS/PF_MEMALLOC*) don't work for FUSE, because
serving a request involves unrelated userspace process(es).

Instead do the same as for dirty pages: make sure the inode is written
before the last reference is gone.

 - fallocate(2)/copy_file_range(2): these call file_update_time() or
   file_modified(), so flush the inode before returning from the call

 - unlink(2), link(2) and rename(2): these call fuse_update_ctime(), so
   flush the ctime directly from this helper

Reported-by: chenguanyou <chenguanyou@xiaomi.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Cc: Ed Tsai <ed.tsai@mediatek.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/fuse/dir.c
fs/fuse/file.c
fs/fuse/fuse_i.h
fs/fuse/inode.c

index d9b977c0f38dc0a482abbba365a9f1ca2141d1fd..2798fbe8d0018c728455c07fd4d63e504705ac16 100644 (file)
@@ -738,11 +738,19 @@ static int fuse_symlink(struct user_namespace *mnt_userns, struct inode *dir,
        return create_new_entry(fm, &args, dir, entry, S_IFLNK);
 }
 
+void fuse_flush_time_update(struct inode *inode)
+{
+       int err = sync_inode_metadata(inode, 1);
+
+       mapping_set_error(inode->i_mapping, err);
+}
+
 void fuse_update_ctime(struct inode *inode)
 {
        if (!IS_NOCMTIME(inode)) {
                inode->i_ctime = current_time(inode);
                mark_inode_dirty_sync(inode);
+               fuse_flush_time_update(inode);
        }
 }
 
index 11404f8c21c75ff193ae487f151cb6762d07742b..5c5ed58d91a73bbc221145f61be72d6530b51f43 100644 (file)
@@ -1848,6 +1848,17 @@ int fuse_write_inode(struct inode *inode, struct writeback_control *wbc)
        struct fuse_file *ff;
        int err;
 
+       /*
+        * Inode is always written before the last reference is dropped and
+        * hence this should not be reached from reclaim.
+        *
+        * Writing back the inode from reclaim can deadlock if the request
+        * processing itself needs an allocation.  Allocations triggering
+        * reclaim while serving a request can't be prevented, because it can
+        * involve any number of unrelated userspace processes.
+        */
+       WARN_ON(wbc->for_reclaim);
+
        ff = __fuse_write_file_get(fi);
        err = fuse_flush_times(inode, ff);
        if (ff)
@@ -3002,6 +3013,8 @@ out:
        if (lock_inode)
                inode_unlock(inode);
 
+       fuse_flush_time_update(inode);
+
        return err;
 }
 
@@ -3111,6 +3124,8 @@ out:
        inode_unlock(inode_out);
        file_accessed(file_in);
 
+       fuse_flush_time_update(inode_out);
+
        return err;
 }
 
index f55f9f94b1a4fbf2c0fbdc67a3a80a6739dc48f3..a59e36c7deaea0dc0dbae159385a85dbbe406e06 100644 (file)
@@ -1148,6 +1148,7 @@ int fuse_allow_current_process(struct fuse_conn *fc);
 
 u64 fuse_lock_owner_id(struct fuse_conn *fc, fl_owner_t id);
 
+void fuse_flush_time_update(struct inode *inode);
 void fuse_update_ctime(struct inode *inode);
 
 int fuse_update_attributes(struct inode *inode, struct file *file);
index 12d49a1914e84801fddf6d9a833409c7e94e9256..2f999d38c9b4a8fe1202734bf20243bb1f115ed2 100644 (file)
@@ -118,6 +118,9 @@ static void fuse_evict_inode(struct inode *inode)
 {
        struct fuse_inode *fi = get_fuse_inode(inode);
 
+       /* Will write inode on close/munmap and in all other dirtiers */
+       WARN_ON(inode->i_state & I_DIRTY_INODE);
+
        truncate_inode_pages_final(&inode->i_data);
        clear_inode(inode);
        if (inode->i_sb->s_flags & SB_ACTIVE) {