Provide a helper to allow ->put_super() to be used to wait for outstanding cache operations that are pinning inodes. The helper has a loop that waits for the first inode that has a non-zero usage and a cookie. It then calls evict_inodes() to reduce the list and loops round again until it finds no more candidate inodes. Without this, evict_inodes() won't get rid of such operations, and the "VFS: Busy inodes ..." message will be displayed and the inode abandoned. Signed-off-by: David Howells <dhowells@xxxxxxxxxx> --- fs/fscache/io.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/fscache.h | 2 ++ 2 files changed, 52 insertions(+) diff --git a/fs/fscache/io.c b/fs/fscache/io.c index 87ffe84c9f27..de9ffc16eb4f 100644 --- a/fs/fscache/io.c +++ b/fs/fscache/io.c @@ -180,3 +180,53 @@ int fscache_set_page_dirty(struct page *page, struct fscache_cookie *cookie) return 1; } EXPORT_SYMBOL(fscache_set_page_dirty); + +/** + * fscache_put_super - Wait for outstanding ops to complete + * @sb: The superblock to wait on + * @get_cookie: Function to get the cookie on an inode + * + * Wait for outstanding cache operations on the inodes of a superblock to + * complete as they might be pinning an inode. This is designed to be called + * from ->put_super(), right before the "VFS: Busy inodes" check. + */ +void fscache_put_super(struct super_block *sb, + struct fscache_cookie *(*get_cookie)(struct inode *inode)) +{ + struct fscache_cookie *cookie; + struct inode *inode, *p; + + while (!list_empty(&sb->s_inodes)) { + /* Find the first inode that we need to wait on */ + inode = NULL; + cookie = NULL; + spin_lock(&sb->s_inode_list_lock); + list_for_each_entry(p, &sb->s_inodes, i_sb_list) { + if (atomic_inc_not_zero(&p->i_count)) { + inode = p; + cookie = get_cookie(inode); + if (!cookie) { + iput(inode); + inode = NULL; + cookie = NULL; + continue; + } + break; + } + } + spin_unlock(&sb->s_inode_list_lock); + + if (inode) { + /* n_ops is kept artificially raised to stop wakeups */ + atomic_dec(&cookie->n_ops); + wait_var_event(&cookie->n_ops, atomic_read(&cookie->n_ops) == 0); + atomic_inc(&cookie->n_ops); + iput(inode); + } + + evict_inodes(sb); + if (!inode) + break; + } +} +EXPORT_SYMBOL(fscache_put_super); diff --git a/include/linux/fscache.h b/include/linux/fscache.h index d2fc98a5755a..38a252b06b54 100644 --- a/include/linux/fscache.h +++ b/include/linux/fscache.h @@ -204,6 +204,8 @@ extern int __fscache_begin_operation(struct fscache_cookie *, struct fscache_op_ extern void __fscache_relinquish_cookie(struct fscache_cookie *, bool); extern void __fscache_update_cookie(struct fscache_cookie *, const void *, const loff_t *); extern void __fscache_invalidate(struct fscache_cookie *, loff_t); +extern void fscache_put_super(struct super_block *, + struct fscache_cookie *(*get_cookie)(struct inode *)); /** * fscache_register_netfs - Register a filesystem as desiring caching services