fscache, cachefiles: Rewrite
Subsystems (13)
David Howells
maintainerPATCH v2 63/67 nfs: Implement cache I/O by accessing the cache directly
In reply to David HowellsMove NFS to using fscache DIO API instead of the old upstream I/O API as that has been removed. This is a stopgap solution as the intention is that at sometime in the future, the cache will move to using larger blocks and won't be able to store individual pages in order to deal with the potential for data corruption due to the backing filesystem being able insert/remove bridging blocks of zeros into its extent list[1].
NFS then reads and writes cache pages synchronously and one page at a time.
The preferred change would be to use the netfs lib, but the new I/O API can be used directly. It's just that as the cache now needs to track data for itself, caching blocks may exceed page size...
This code is somewhat borrowed from my "fallback I/O" patchset[2].
---
fs/fscache/io.c | 8 +++ fs/nfs/fscache.c | 126 ++++++++++++++++++++++++++++++++++++++--------- fs/nfs/fscache.h | 56 ++++----------------- fs/nfs/read.c | 25 +++------ fs/nfs/write.c | 7 ++- include/linux/fscache.h | 28 ++++++++++ 6 files changed, 163 insertions(+), 87 deletions(-)
fs/fscache/io.c +8 -0
@@ -150,6 +150,14 @@ int __fscache_begin_read_operation(struct netfs_cache_resources *cres, }EXPORT_SYMBOL (__fscache_begin_read_operation); +int __fscache_begin_write_operation (struct netfs_cache_resources *cres, +struct fscache_cookie *cookie) +{ +return fscache_begin_operation (cres, cookie,FSCACHE_WANT_PARAMS , + fscache_access_io_write); +} +EXPORT_SYMBOL (__fscache_begin_write_operation); +/** * fscache_set_page_dirty - Mark page dirty and pin a cache object for writeback * @page: The page being dirtied
fs/nfs/fscache.c +103 -23
151 lines not on this page · raw message
fs/nfs/fscache.h +11 -45
89 lines not on this page · raw message
fs/nfs/read.c +8 -17
@@ -123,7 +123,7 @@ static void nfs_readpage_release(struct nfs_page *req, int error)struct address_space *mapping =page_file_mapping (page);if (PageUptodate (page)) -nfs_readpage_to_fscache (inode, page,0 ); +nfs_readpage_to_fscache (inode, page);else if (!PageError (page) && !PagePrivate (page))generic_error_remove_page (mapping, page);unlock_page (page);
@@ -305,6 +305,12 @@ readpage_async_filler(void *data, struct page *page) aligned_len = min_t(unsigned int, ALIGN(len, rsize), PAGE_SIZE); +if (!IS_SYNC (page->mapping->host)) { + error =nfs_readpage_from_fscache (page->mapping->host, page); +if (error ==0 ) + goto out_unlock; + } + new =nfs_create_request (desc->ctx, page,0 , aligned_len);if (IS_ERR (new)) goto out_error;
@@ -320,6 +326,7 @@ readpage_async_filler(void *data, struct page *page)return 0 ; out_error: error =PTR_ERR (new); +out_unlock:unlock_page (page); out:return error;
@@ -366,12 +373,6 @@ int nfs_readpage(struct file *file, struct page *page) desc.ctx =get_nfs_open_context (nfs_file_open_context (file));xchg (&desc.ctx->error,0 ); -if (!IS_SYNC (inode)) { - ret =nfs_readpage_from_fscache (desc.ctx, inode, page); -if (ret ==0 ) - goto out_wait; - } -nfs_pageio_init_read (&desc.pgio, inode, false, &nfs_async_read_completion_ops);
@@ -381,7 +382,6 @@ int nfs_readpage(struct file *file, struct page *page)nfs_pageio_complete_read (&desc.pgio); ret = desc.pgio.pg_error <0 ? desc.pgio.pg_error :0 ; -out_wait:if (!ret) { ret =wait_on_page_locked_killable (page);if (!PageUptodate (page) && !ret)
@@ -419,14 +419,6 @@ int nfs_readpages(struct file *file, struct address_space *mapping, }else desc.ctx =get_nfs_open_context (nfs_file_open_context (file)); -/* attempt to read as many of the pages as possible from the cache -* - this returns -ENOBUFS immediately if the cookie is negative -*/ - ret =nfs_readpages_from_fscache (desc.ctx, inode, mapping, - pages, &nr_pages); -if (ret ==0 ) - goto read_complete;/* all pages were read */ -nfs_pageio_init_read (&desc.pgio, inode, false, &nfs_async_read_completion_ops);
@@ -434,7 +426,6 @@ int nfs_readpages(struct file *file, struct address_space *mapping,nfs_pageio_complete_read (&desc.pgio); -read_complete:put_nfs_open_context (desc.ctx); out:trace_nfs_aop_readahead_done (inode, nr_pages, ret);
fs/nfs/write.c +5 -2
@@ -2126,8 +2126,11 @@ int nfs_migrate_page(struct address_space *mapping, struct page *newpage,if (PagePrivate (page))return -EBUSY ; -if (!nfs_fscache_release_page (page,GFP_KERNEL )) -return -EBUSY ; +if (PageFsCache (page)) { +if (mode ==MIGRATE_ASYNC ) +return -EBUSY ; +wait_on_page_fscache (page); + }return migrate_page (mapping, newpage, page, mode); }
include/linux/fscache.h +28 -0
@@ -165,6 +165,7 @@ extern void __fscache_relinquish_cookie(struct fscache_cookie *, bool);extern void __fscache_resize_cookie (struct fscache_cookie *,loff_t );extern void __fscache_invalidate (struct fscache_cookie *,const void *,loff_t ,unsigned int );extern int __fscache_begin_read_operation (struct netfs_cache_resources *,struct fscache_cookie *); +extern int __fscache_begin_write_operation (struct netfs_cache_resources *,struct fscache_cookie *); extern void __fscache_write_to_cache(struct fscache_cookie *, struct address_space *, loff_t, size_t, loff_t, netfs_io_terminated_t, void *,
@@ -493,6 +494,33 @@ int fscache_read(struct netfs_cache_resources *cres, term_func, term_func_priv); } +/** +* fscache_begin_write_operation - Begin a write operation for the netfs lib +* @cres: The cache resources for the write being performed +* @cookie: The cookie representing the cache object +* +* Begin a write operation on behalf of the netfs helper library. @cres +* indicates the cache resources to which the operation state should be +* attached; @cookie indicates the cache object that will be accessed. +* +* @cres->inval_counter is set from @cookie->inval_counter for comparison at +* the end of the operation. This allows invalidation during the operation to +* be detected by the caller. +* +* Returns: +* * 0 - Success +* * -ENOBUFS - No caching available +* * Other error code from the cache, such as -ENOMEM. +*/ +static inline +int fscache_begin_write_operation (struct netfs_cache_resources *cres, +struct fscache_cookie *cookie) +{ +if (fscache_cookie_enabled (cookie)) +return __fscache_begin_write_operation (cres, cookie); +return -ENOBUFS ; +} +/** * fscache_write - Start a write to the cache. * @cres: The cache resources to use
David Howells
maintainerPATCH v2 64/67 cifs: Support fscache indexing rewrite (untested)
In reply to David HowellsChange the cifs filesystem to take account of the changes to fscache's indexing rewrite and reenable caching in cifs.
The following changes have been made:
(1) The fscache_netfs struct is no more, and there's no need to register the filesystem as a whole.
(2) The session cookie is now an fscache_volume cookie, allocated with fscache_acquire_volume(). That takes three parameters: a string representing the "volume" in the index, a string naming the cache to use (or NULL) and a u64 that conveys coherency metadata for the volume.
For cifs, I've made it render the volume name string as:
"cifs,<ipaddress>,<sharename>"
where the sharename has '/' characters replaced with ';'.
This probably needs rethinking a bit as the total name could exceed the maximum filename component length.
Further, the coherency data is currently just set to 0. It needs something else doing with it - I wonder if it would suffice simply to sum the resource_id, vol_create_time and vol_serial_number or maybe hash them.
(3) The fscache_cookie_def is no more and needed information is passed directly to fscache_acquire_cookie(). The cache no longer calls back into the filesystem, but rather metadata changes are indicated at other times.
fscache_acquire_cookie() is passed the same keying and coherency information as before.
(4) The functions to set/reset cookies are removed and fscache_use_cookie() and fscache_unuse_cookie() are used instead.
fscache_use_cookie() is passed a flag to indicate if the cookie is opened for writing. fscache_unuse_cookie() is passed updates for the metadata if we changed it (ie. if the file was opened for writing).
These are called when the file is opened or closed.
(5) cifs_setattr_*() are made to call fscache_resize() to change the size of the cache object.
(6) The functions to read and write data are stubbed out pending a conversion to use netfslib.
Changes
=======
ver #2:
- Use gfpflags_allow_blocking() rather than using flag directly.
- Upgraded to -rc4 to allow for upstream changes[1].
- fscache_acquire_volume() now returns errors.
---
fs/cifs/Kconfig | 2 fs/cifs/Makefile | 2 fs/cifs/cache.c | 105 ----------------- fs/cifs/cifsfs.c | 11 -- fs/cifs/cifsglob.h | 5 - fs/cifs/connect.c | 12 -- fs/cifs/file.c | 64 +++++++--- fs/cifs/fscache.c | 319 ++++++++++++---------------------------------------- fs/cifs/fscache.h | 106 +++++------------ fs/cifs/inode.c | 36 ++++-- 10 files changed, 175 insertions(+), 487 deletions(-) delete mode 100644 fs/cifs/cache.c
fs/cifs/Kconfig +1 -1
@@ -188,7 +188,7 @@ config CIFS_SMB_DIRECT config CIFS_FSCACHE bool "Provide CIFS client caching support" - depends on CIFS=m && FSCACHE_OLD_API || CIFS=y && FSCACHE_OLD_API=y + depends on CIFS=m && FSCACHE || CIFS=y && FSCACHE=y help Makes CIFS FS-Cache capable. Say Y here if you want your CIFS data to be cached locally on disk through the general filesystem cache
fs/cifs/Makefile +1 -1
@@ -25,7 +25,7 @@ cifs-$(CONFIG_CIFS_DFS_UPCALL) += cifs_dfs_ref.o dfs_cache.o cifs-$(CONFIG_CIFS_SWN_UPCALL) += netlink.o cifs_swn.o -cifs-$(CONFIG_CIFS_FSCACHE) += fscache.o cache.o +cifs-$(CONFIG_CIFS_FSCACHE) += fscache.o cifs-$(CONFIG_CIFS_SMB_DIRECT) += smbdirect.o
fs/cifs/cache.c deleted +0 -105
105 lines not on this page · raw message
fs/cifs/cifsfs.c +3 -8
@@ -396,6 +396,8 @@ static void cifs_evict_inode(struct inode *inode) {truncate_inode_pages_final (&inode->i_data); +if (inode->i_state &I_PINNING_FSCACHE_WB ) +cifs_fscache_unuse_inode_cookie (inode, true);clear_inode (inode); }
@@ -1624,13 +1626,9 @@ init_cifs(void) goto out_destroy_cifsoplockd_wq; } - rc =cifs_fscache_register (); -if (rc) - goto out_destroy_deferredclose_wq; - rc =cifs_init_inodecache ();if (rc) - goto out_unreg_fscache; + goto out_destroy_deferredclose_wq; rc =cifs_init_mids ();if (rc)
@@ -1692,8 +1690,6 @@ init_cifs(void)cifs_destroy_mids (); out_destroy_inodecache:cifs_destroy_inodecache (); -out_unreg_fscache: -cifs_fscache_unregister (); out_destroy_deferredclose_wq:destroy_workqueue (deferredclose_wq); out_destroy_cifsoplockd_wq:
@@ -1729,7 +1725,6 @@ exit_cifs(void)cifs_destroy_request_bufs ();cifs_destroy_mids ();cifs_destroy_inodecache (); -cifs_fscache_unregister ();destroy_workqueue (deferredclose_wq);destroy_workqueue (cifsoplockd_wq);destroy_workqueue (decrypt_wq);
fs/cifs/cifsglob.h +1 -4
@@ -659,9 +659,6 @@ struct TCP_Server_Info {unsigned int total_read;/* total amount of data read in this pass */ atomic_t in_send;/* requests trying to send */ atomic_t num_waiters;/* blocked waiting to get in sendrecv */ -#ifdef CONFIG_CIFS_FSCACHE -struct fscache_cookie *fscache;/* client index cache cookie */ -#endif #ifdef CONFIG_CIFS_STATS2 atomic_t num_cmds[NUMBER_OF_SMB2_COMMANDS ];/* total requests by cmd */ atomic_t smb2slowcmd[NUMBER_OF_SMB2_COMMANDS ];/* count resps > 1 sec */
@@ -1117,7 +1114,7 @@ struct cifs_tcon {__u32 max_bytes_copy;#ifdef CONFIG_CIFS_FSCACHE u64 resource_id;/* server resource id */ -struct fscache_cookie *fscache;/* cookie for share */ +struct fscache_volume *fscache;/* cookie for share */ #endif struct list_head pending_opens;/* list of incomplete opens */ struct cached_fid crfid;/* Cached root fid */
fs/cifs/connect.c +0 -12
@@ -1396,10 +1396,6 @@ cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)cifs_crypto_secmech_release (server); -/* fscache server cookies are based on primary channel only */ -if (!CIFS_SERVER_IS_CHAN (server)) -cifs_fscache_release_client_cookie (server); -kfree (server->session_key.response); server->session_key.response =NULL ; server->session_key.len =0 ;
@@ -1559,14 +1555,6 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,list_add (&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list);spin_unlock (&cifs_tcp_ses_lock); -/* fscache server cookies are based on primary channel only */ -if (!CIFS_SERVER_IS_CHAN (tcp_ses)) -cifs_fscache_get_client_cookie (tcp_ses); -#ifdef CONFIG_CIFS_FSCACHE - else - tcp_ses->fscache = tcp_ses->primary_server->fscache; -#endif /* CONFIG_CIFS_FSCACHE */ -/* queue echo request delayed work */ queue_delayed_work (cifsiod_wq, &tcp_ses->echo, tcp_ses->echo_interval);
fs/cifs/file.c +43 -21
136 lines not on this page · raw message
fs/cifs/fscache.c +74 -245
402 lines not on this page · raw message
fs/cifs/fscache.h +29 -77
154 lines not on this page · raw message
fs/cifs/inode.c +23 -13
@@ -1298,10 +1298,7 @@ cifs_iget(struct super_block *sb, struct cifs_fattr *fattr) inode->i_flags |=S_NOATIME |S_NOCMTIME ;if (inode->i_state &I_NEW ) { inode->i_ino = hash; -#ifdef CONFIG_CIFS_FSCACHE -/* initialize per-inode cache cookie pointer */ -CIFS_I (inode)->fscache =NULL ; -#endif +cifs_fscache_get_inode_cookie (inode);unlock_new_inode (inode); } }
@@ -1376,12 +1373,18 @@ struct inode *cifs_root_iget(struct super_block *sb) inode =ERR_PTR (rc); } -/* -* The cookie is initialized from volume info returned above. -* Inside cifs_fscache_get_super_cookie it checks -* that we do not get super cookie twice. -*/ -cifs_fscache_get_super_cookie (tcon); +if (!rc) { +/* +* The cookie is initialized from volume info returned above. +* Inside cifs_fscache_get_super_cookie it checks +* that we do not get super cookie twice. +*/ + rc =cifs_fscache_get_super_cookie (tcon); +if (rc <0 ) { +iget_failed (inode); + inode =ERR_PTR (rc); + } + } out:kfree (path);
@@ -2270,6 +2273,8 @@ cifs_dentry_needs_reval(struct dentry *dentry)int cifs_invalidate_mapping (struct inode *inode) { +struct cifs_fscache_inode_auxdata auxdata; +struct cifsInodeInfo *cifsi =CIFS_I (inode);int rc =0 ;if (inode->i_mapping && inode->i_mapping->nrpages !=0 ) {
@@ -2279,7 +2284,8 @@ cifs_invalidate_mapping(struct inode *inode) __func__, inode); } -cifs_fscache_reset_inode_cookie (inode); +cifs_fscache_fill_auxdata (&cifsi->vfs_inode, &auxdata); +fscache_invalidate (cifs_inode_cookie (inode), &auxdata,i_size_read (inode),0 );return rc; }
@@ -2784,8 +2790,10 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) goto out;if ((attrs->ia_valid &ATTR_SIZE ) && - attrs->ia_size !=i_size_read (inode)) + attrs->ia_size !=i_size_read (inode)) {truncate_setsize (inode, attrs->ia_size); +fscache_resize_cookie (cifs_inode_cookie (inode), attrs->ia_size); + }setattr_copy (&init_user_ns, inode, attrs);mark_inode_dirty (inode);
@@ -2980,8 +2988,10 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) goto cifs_setattr_exit;if ((attrs->ia_valid &ATTR_SIZE ) && - attrs->ia_size !=i_size_read (inode)) + attrs->ia_size !=i_size_read (inode)) {truncate_setsize (inode, attrs->ia_size); +fscache_resize_cookie (cifs_inode_cookie (inode), attrs->ia_size); + }setattr_copy (&init_user_ns, inode, attrs);mark_inode_dirty (inode);
David Howells
maintainerPATCH v2 65/67 ceph: conversion to new fscache API
In reply to David HowellsFrom: Jeff Layton <[email protected]>
Now that the fscache API has been reworked and simplified, change ceph over to use it.
With the old API, we would only instantiate a cookie when the file was open for reads. Change it to instantiate the cookie when the inode is instantiated and call use/unuse when the file is opened/closed.
Also, ensure we resize the cached data on truncates, and invalidate the cache in response to the appropriate events. This will allow us to plumb in write support later.
---
fs/ceph/Kconfig | 2 - fs/ceph/addr.c | 34 +++++---- fs/ceph/cache.c | 218 +++++++++++++++---------------------------------------- fs/ceph/cache.h | 97 +++++++++++++++++------- fs/ceph/caps.c | 3 + fs/ceph/file.c | 13 +++ fs/ceph/inode.c | 22 ++++-- fs/ceph/super.c | 10 --- fs/ceph/super.h | 3 - 9 files changed, 178 insertions(+), 224 deletions(-)
fs/ceph/Kconfig +1 -1
@@ -21,7 +21,7 @@ config CEPH_FS if CEPH_FS config CEPH_FSCACHE bool "Enable Ceph client caching support" - depends on CEPH_FS=m && FSCACHE_OLD_API || CEPH_FS=y && FSCACHE_OLD_API=y + depends on CEPH_FS=m && FSCACHE || CEPH_FS=y && FSCACHE=y help Choose Y here to enable persistent, read-only local caching support for Ceph clients using FS-Cache
fs/ceph/addr.c +20 -14
@@ -126,7 +126,7 @@ static int ceph_set_page_dirty(struct page *page)BUG_ON (PagePrivate (page));attach_page_private (page, snapc); -return __set_page_dirty_nobuffers (page); +return ceph_fscache_set_page_dirty (page); }/*
@@ -141,8 +141,6 @@ static void ceph_invalidatepage(struct page *page, unsigned int offset,struct ceph_inode_info *ci;struct ceph_snap_context *snapc; -wait_on_page_fscache (page); - inode = page->mapping->host; ci =ceph_inode (inode);
@@ -153,28 +151,36 @@ static void ceph_invalidatepage(struct page *page, unsigned int offset, }WARN_ON (!PageLocked (page)); -if (!PagePrivate (page)) -return ; +if (PagePrivate (page)) { +dout ("%p invalidatepage %p idx %lu full dirty page\n" , + inode, page, page->index); -dout ("%p invalidatepage %p idx %lu full dirty page\n" , - inode, page, page->index); + snapc =detach_page_private (page); +ceph_put_wrbuffer_cap_refs (ci,1 , snapc); +ceph_put_snap_context (snapc); + } - snapc =detach_page_private (page); -ceph_put_wrbuffer_cap_refs (ci,1 , snapc); -ceph_put_snap_context (snapc); +wait_on_page_fscache (page); }static int ceph_releasepage (struct page *page,gfp_t gfp) { -dout ("%p releasepage %p idx %lu (%sdirty)\n" , page->mapping->host, - page, page->index,PageDirty (page) ?"" :"not " ); +struct inode *inode = page->mapping->host; + +dout ("%llx:%llx releasepage %p idx %lu (%sdirty)\n" , +ceph_vinop (inode), page, + page->index,PageDirty (page) ?"" :"not " ); + +if (PagePrivate (page)) +return 0 ;if (PageFsCache (page)) { -if (!(gfp & __GFP_DIRECT_RECLAIM) || !(gfp & __GFP_FS)) +if (!gfpflags_allow_blocking (gfp) || !(gfp & __GFP_FS))return 0 ;wait_on_page_fscache (page); } -return !PagePrivate (page); +ceph_fscache_note_page_release (inode); +return 1 ; }static void ceph_netfs_expand_readahead (struct netfs_read_request *rreq)
fs/ceph/cache.c +59 -159
258 lines not on this page · raw message
fs/ceph/cache.h +68 -29
158 lines not on this page · raw message
fs/ceph/caps.c +2 -1
@@ -1856,7 +1856,7 @@ static int try_nonblocking_invalidate(struct inode *inode)u32 invalidating_gen = ci->i_rdcache_gen;spin_unlock (&ci->i_ceph_lock); -ceph_fscache_invalidate (inode); +ceph_fscache_invalidate (inode, false);invalidate_mapping_pages (&inode->i_data,0 ,-1 );spin_lock (&ci->i_ceph_lock);
@@ -2388,6 +2388,7 @@ int ceph_write_inode(struct inode *inode, struct writeback_control *wbc)int wait = (wbc->sync_mode ==WB_SYNC_ALL && !wbc->for_sync);dout ("write_inode %p wait=%d\n" , inode, wait); +ceph_fscache_unpin_writeback (inode, wbc);if (wait) { dirty =try_flush_caps (inode, &flush_tid);if (dirty)
fs/ceph/file.c +10 -3
@@ -248,8 +248,7 @@ static int ceph_init_file(struct inode *inode, struct file *file, int fmode)switch (inode->i_mode &S_IFMT ) {case S_IFREG : -ceph_fscache_register_inode_cookie (inode); -ceph_fscache_file_set_cookie (inode, file); +ceph_fscache_use_cookie (inode, file->f_mode &FMODE_WRITE ); fallthrough;case S_IFDIR : ret = ceph_init_file_info(inode, file, fmode,
@@ -810,6 +809,7 @@ int ceph_release(struct inode *inode, struct file *file)dout ("release inode %p regular file %p\n" , inode, file);WARN_ON (!list_empty (&fi->rw_contexts)); +ceph_fscache_unuse_cookie (inode, file->f_mode &FMODE_WRITE );ceph_put_fmode (ci, fi->fmode,1 );kmem_cache_free (ceph_file_cachep, fi);
@@ -1206,7 +1206,11 @@ ceph_direct_read_write(struct kiocb *iocb, struct iov_iter *iter, snapc, snapc ? snapc->seq : 0);if (write) { -int ret2 =invalidate_inode_pages2_range (inode->i_mapping, +int ret2; + +ceph_fscache_invalidate (inode, true); + + ret2 =invalidate_inode_pages2_range (inode->i_mapping, pos >>PAGE_SHIFT , (pos + count -1 ) >>PAGE_SHIFT );if (ret2 <0 )
@@ -1417,6 +1421,7 @@ ceph_sync_write(struct kiocb *iocb, struct iov_iter *from, loff_t pos,if (ret <0 )return ret; +ceph_fscache_invalidate (inode, false); ret =invalidate_inode_pages2_range (inode->i_mapping, pos >>PAGE_SHIFT , (pos + count -1 ) >>PAGE_SHIFT );
@@ -2101,6 +2106,7 @@ static long ceph_fallocate(struct file *file, int mode, goto unlock;filemap_invalidate_lock (inode->i_mapping); +ceph_fscache_invalidate (inode, false);ceph_zero_pagecache_range (inode, offset, length); ret =ceph_zero_objects (inode, offset, length);
@@ -2425,6 +2431,7 @@ static ssize_t __ceph_copy_file_range(struct file *src_file, loff_t src_off, goto out_caps;/* Drop dst file cached pages */ +ceph_fscache_invalidate (dst_inode, false); ret =invalidate_inode_pages2_range (dst_inode->i_mapping, dst_off >>PAGE_SHIFT , (dst_off + len) >>PAGE_SHIFT );
fs/ceph/inode.c +16 -6
@@ -564,6 +564,8 @@ void ceph_evict_inode(struct inode *inode)percpu_counter_dec (&mdsc->metric.total_inodes);truncate_inode_pages_final (&inode->i_data); +if (inode->i_state &I_PINNING_FSCACHE_WB ) +ceph_fscache_unuse_cookie (inode, true);clear_inode (inode);ceph_fscache_unregister_inode_cookie (ci);
@@ -634,6 +636,12 @@ int ceph_fill_file_size(struct inode *inode, int issued, }i_size_write (inode, size); inode->i_blocks =calc_inode_blocks (size); +/* +* If we're expanding, then we should be able to just update +* the existing cookie. +*/ +if (size > isize) +ceph_fscache_update (inode); ci->i_reported_size = size;if (truncate_seq != ci->i_truncate_seq) { dout("truncate_seq %u -> %u\n",
@@ -666,10 +674,6 @@ int ceph_fill_file_size(struct inode *inode, int issued, truncate_size); ci->i_truncate_size = truncate_size; } - -if (queue_trunc) -ceph_fscache_invalidate (inode); -return queue_trunc; }
@@ -1053,6 +1057,8 @@ int ceph_fill_inode(struct inode *inode, struct page *locked_page,spin_unlock (&ci->i_ceph_lock); +ceph_fscache_register_inode_cookie (inode); +if (fill_inline)ceph_fill_inline_data (inode, locked_page, iinfo->inline_data, iinfo->inline_len);
@@ -1814,11 +1820,13 @@ bool ceph_inode_set_size(struct inode *inode, loff_t size)spin_lock (&ci->i_ceph_lock);dout ("set_size %p %llu -> %llu\n" , inode,i_size_read (inode), size);i_size_write (inode, size); +ceph_fscache_update (inode); inode->i_blocks =calc_inode_blocks (size); ret =__ceph_should_report_size (ci);spin_unlock (&ci->i_ceph_lock); +return ret; }
@@ -1844,6 +1852,8 @@ static void ceph_do_invalidate_pages(struct inode *inode)u32 orig_gen;int check =0 ; +ceph_fscache_invalidate (inode, false); +mutex_lock (&ci->i_truncate_mutex);if (ceph_inode_is_shutdown (inode)) {
@@ -1868,7 +1878,7 @@ static void ceph_do_invalidate_pages(struct inode *inode) orig_gen = ci->i_rdcache_gen;spin_unlock (&ci->i_ceph_lock); -ceph_fscache_invalidate (inode); +ceph_fscache_invalidate (inode, false);if (invalidate_inode_pages2 (inode->i_mapping) <0 ) {pr_err ("invalidate_inode_pages2 %llx.%llx failed\n" ,ceph_vinop (inode));
@@ -1937,6 +1947,7 @@ void __ceph_do_pending_vmtruncate(struct inode *inode) ci->i_truncate_pending, to);spin_unlock (&ci->i_ceph_lock); +ceph_fscache_resize (inode, to);truncate_pagecache (inode, to);spin_lock (&ci->i_ceph_lock);
@@ -2184,7 +2195,6 @@ int __ceph_setattr(struct inode *inode, struct iattr *attr)if (inode_dirty_flags)__mark_inode_dirty (inode, inode_dirty_flags); -if (mask) { req->r_inode = inode;ihold (inode);
fs/ceph/super.c +1 -9
@@ -787,16 +787,10 @@ static int __init init_caches(void)if (!ceph_wb_pagevec_pool) goto bad_pagevec_pool; - error =ceph_fscache_register (); -if (error) - goto bad_fscache; -return 0 ; -bad_fscache: -kmem_cache_destroy (ceph_mds_request_cachep); bad_pagevec_pool: -mempool_destroy (ceph_wb_pagevec_pool); +kmem_cache_destroy (ceph_mds_request_cachep); bad_mds_req:kmem_cache_destroy (ceph_dir_file_cachep); bad_dir_file:
@@ -828,8 +822,6 @@ static void destroy_caches(void)kmem_cache_destroy (ceph_dir_file_cachep);kmem_cache_destroy (ceph_mds_request_cachep);mempool_destroy (ceph_wb_pagevec_pool); - -ceph_fscache_unregister (); }static void __ceph_umount_begin (struct ceph_fs_client *fsc)
fs/ceph/super.h +1 -2
@@ -21,7 +21,6 @@#include <linux/ceph/libceph.h> #ifdef CONFIG_CEPH_FSCACHE -#define FSCACHE_USE_NEW_IO_API #include <linux/fscache.h> #endif
@@ -135,7 +134,7 @@ struct ceph_fs_client {#endif #ifdef CONFIG_CEPH_FSCACHE -struct fscache_cookie *fscache; +struct fscache_volume *fscache;#endif };
David Howells
maintainerPATCH v2 66/67 ceph: add fscache writeback support
In reply to David HowellsFrom: Jeff Layton <[email protected]>
When updating the backing store from the pagecache (a'la writepage or writepages), write to the cache first. This allows us to keep caching files even when they are being written, as long as we have appropriate caps.
---
fs/ceph/addr.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 8 deletions(-)
fs/ceph/addr.c +59 -8
@@ -5,7 +5,6 @@#include <linux/fs.h> #include <linux/mm.h> #include <linux/pagemap.h> -#include <linux/writeback.h> /* generic_writepages */ #include <linux/slab.h> #include <linux/pagevec.h> #include <linux/task_io_accounting_ops.h>
@@ -384,6 +383,38 @@ static void ceph_readahead(struct readahead_control *ractl)netfs_readahead (ractl, &ceph_netfs_read_ops, (void *)(uintptr_t )got); } +#ifdef CONFIG_CEPH_FSCACHE +static void ceph_set_page_fscache (struct page *page) +{ +set_page_fscache (page); +} + +static void ceph_fscache_write_terminated (void *priv,ssize_t error,bool was_async) +{ +struct inode *inode = priv; + +if (IS_ERR_VALUE (error) && error != -ENOBUFS ) +ceph_fscache_invalidate (inode, false); +} + +static void ceph_fscache_write_to_cache (struct inode *inode,u64 off,u64 len,bool caching) +{ +struct ceph_inode_info *ci =ceph_inode (inode); +struct fscache_cookie *cookie =ceph_fscache_cookie (ci); + +fscache_write_to_cache (cookie, inode->i_mapping, off, len,i_size_read (inode), + ceph_fscache_write_terminated, inode, caching); +} +#else +static inline void ceph_set_page_fscache (struct page *page) +{ +} + +static inline void ceph_fscache_write_to_cache (struct inode *inode,u64 off,u64 len,bool caching) +{ +} +#endif /* CONFIG_CEPH_FSCACHE */ +struct ceph_writeback_ctl {loff_t i_size;
@@ -499,6 +530,7 @@ static int writepage_nounlock(struct page *page, struct writeback_control *wbc)struct ceph_writeback_ctl ceph_wbc;struct ceph_osd_client *osdc = &fsc->client->osdc;struct ceph_osd_request *req; +bool caching =ceph_is_cache_enabled (inode);dout ("writepage %p idx %lu\n" , page, page->index);
@@ -537,16 +569,17 @@ static int writepage_nounlock(struct page *page, struct writeback_control *wbc) CONGESTION_ON_THRESH(fsc->mount_options->congestion_kb))set_bdi_congested (inode_to_bdi (inode),BLK_RW_ASYNC ); -set_page_writeback (page); req =ceph_osdc_new_request (osdc, &ci->i_layout,ceph_vino (inode), page_off, &len,0 ,1 ,CEPH_OSD_OP_WRITE ,CEPH_OSD_FLAG_WRITE , snapc, ceph_wbc.truncate_seq, ceph_wbc.truncate_size, true); -if (IS_ERR (req)) { -redirty_page_for_writepage (wbc, page); -end_page_writeback (page); +if (IS_ERR (req))return PTR_ERR (req); - } + +set_page_writeback (page); +if (caching) +ceph_set_page_fscache (page); +ceph_fscache_write_to_cache (inode, page_off, len, caching);/* it may be a short write due to an object boundary */ WARN_ON_ONCE (len >thp_size (page));
@@ -605,6 +638,9 @@ static int ceph_writepage(struct page *page, struct writeback_control *wbc)struct inode *inode = page->mapping->host;BUG_ON (!inode);ihold (inode); + +wait_on_page_fscache (page); + err =writepage_nounlock (page, wbc);if (err == -ERESTARTSYS ) {/* direct memory reclaimer was killed by SIGKILL. return 0
@@ -726,6 +762,7 @@ static int ceph_writepages_start(struct address_space *mapping,struct ceph_writeback_ctl ceph_wbc;bool should_loop, range_whole = false;bool done = false; +bool caching =ceph_is_cache_enabled (inode); dout("writepages_start %p (mode=%s)\n", inode, wbc->sync_mode == WB_SYNC_NONE ? "NONE" :
@@ -849,7 +886,7 @@ static int ceph_writepages_start(struct address_space *mapping,unlock_page (page);break ; } -if (PageWriteback (page)) { +if (PageWriteback (page) ||PageFsCache (page)) {if (wbc->sync_mode ==WB_SYNC_NONE ) {dout ("%p under writeback\n" , page);unlock_page (page);
@@ -857,6 +894,7 @@ static int ceph_writepages_start(struct address_space *mapping, }dout ("waiting on writeback %p\n" , page);wait_on_page_writeback (page); +wait_on_page_fscache (page); }if (!clear_page_dirty_for_io (page)) {
@@ -989,9 +1027,19 @@ static int ceph_writepages_start(struct address_space *mapping, op_idx =0 ;for (i =0 ; i < locked_pages; i++) {u64 cur_offset =page_offset (pages[i]); +/* +* Discontinuity in page range? Ceph can handle that by just passing +* multiple extents in the write op. +*/ if (offset + len != cur_offset) { +/* If it's full, stop here */ if (op_idx +1 == req->r_num_ops)break ; + +/* Kick off an fscache write with what we have so far. */ +ceph_fscache_write_to_cache (inode, offset, len, caching); + +/* Start a new extent */ osd_req_op_extent_dup_last (req, op_idx, cur_offset - offset); dout("writepages got pages at %llu~%llu\n",
@@ -1002,14 +1050,17 @@ static int ceph_writepages_start(struct address_space *mapping,osd_req_op_extent_update (req, op_idx, len); len =0 ; - offset = cur_offset; + offset = cur_offset; data_pages = pages + i; op_idx++; }set_page_writeback (pages[i]); +if (caching) +ceph_set_page_fscache (pages[i]); len +=thp_size (page); } +ceph_fscache_write_to_cache (inode, offset, len, caching);if (ceph_wbc.size_stable) { len =min (len, ceph_wbc.i_size - offset);
David Howells
maintainerPATCH v2 67/67 fscache: Rewrite documentation
In reply to David HowellsRewrite the fscache documentation.
Changes
=======
ver #2:
- Put quoting around some bits of C being referred to in the docs[1].
- Stripped the markup off the ref to the netfs lib doc[2].
---
Documentation/filesystems/caching/backend-api.rst | 847 ++++++---------- Documentation/filesystems/caching/cachefiles.rst | 6 Documentation/filesystems/caching/fscache.rst | 525 +++------- Documentation/filesystems/caching/index.rst | 4 Documentation/filesystems/caching/netfs-api.rst | 1083 ++++++--------------- Documentation/filesystems/caching/object.rst | 313 ------ Documentation/filesystems/caching/operations.rst | 210 ---- Documentation/filesystems/netfs_library.rst | 16 8 files changed, 783 insertions(+), 2221 deletions(-) delete mode 100644 Documentation/filesystems/caching/object.rst delete mode 100644 Documentation/filesystems/caching/operations.rst
Documentation/filesystems/caching/backend-api.rst +298 -549
@@ -1,727 +1,476 @@ .. SPDX-License-Identifier: GPL-2.0 -========================== -FS-Cache Cache backend API -========================== +================= +Cache Backend API +================= The FS-Cache system provides an API by which actual caches can be supplied to FS-Cache for it to then serve out to network filesystems and other interested -parties. +parties. This API is used by:: -This API is declared in <linux/fscache-cache.h>. + #include <linux/fscache-cache.h>. -Initialising and Registering a Cache -==================================== - -To start off, a cache definition must be initialised and registered for each -cache the backend wants to make available. For instance, CacheFS does this in -the fill_super() operation on mounting. - -The cache definition (struct fscache_cache) should be initialised by calling:: +Overview +======== - void fscache_init_cache(struct fscache_cache *cache, - struct fscache_cache_ops *ops, - const char *idfmt, - ...); +Interaction with the API is handled on three levels: cache, volume and data +storage, and each level has its own type of cookie object: -Where: + ======================= ======================= + COOKIE C TYPE + ======================= ======================= + Cache cookie struct fscache_cache + Volume cookie struct fscache_volume + Data storage cookie struct fscache_cookie + ======================= ======================= - * "cache" is a pointer to the cache definition; +Cookies are used to provide some filesystem data to the cache, manage state and +pin the cache during access in addition to acting as reference points for the +API functions. Each cookie has a debugging ID that is included in trace points +to make it easier to correlate traces. Note, though, that debugging IDs are +simply allocated from incrementing counters and will eventually wrap. - * "ops" is a pointer to the table of operations that the backend supports on - this cache; and +The cache backend and the network filesystem can both ask for cache cookies - +and if they ask for one of the same name, they'll get the same cookie. Volume +and data cookies, however, are created at the behest of the filesystem only. - * "idfmt" is a format and printf-style arguments for constructing a label - for the cache. +Cache Cookies +============= -The cache should then be registered with FS-Cache by passing a pointer to the -previously initialised cache definition to:: +Caches are represented in the API by cache cookies. These are objects of +type:: - int fscache_add_cache(struct fscache_cache *cache, - struct fscache_object *fsdef, - const char *tagname); - -Two extra arguments should also be supplied: + struct fscache_cache { + void *cache_priv; + unsigned int debug_id; + char *name; + ... + }; - * "fsdef" which should point to the object representation for the FS-Cache - master index in this cache. Netfs primary index entries will be created - here. FS-Cache keeps the caller's reference to the index object if - successful and will release it upon withdrawal of the cache. +There are a few fields that the cache backend might be interested in. The +``debug_id`` can be used in tracing to match lines referring to the same cache +and ``name`` is the name the cache was registered with. The ``cache_priv`` +member is private data provided by the cache when it is brought online. The +other fields are for internal use. - * "tagname" which, if given, should be a text string naming this cache. If - this is NULL, the identifier will be used instead. For CacheFS, the - identifier is set to name the underlying block device and the tag can be - supplied by mount. -This function may return -ENOMEM if it ran out of memory or -EEXIST if the tag -is already in use. 0 will be returned on success. +Registering a Cache +=================== +When a cache backend wants to bring a cache online, it should first register +the cache name and that will get it a cache cookie. This is done with:: -Unregistering a Cache -===================== + struct fscache_cache *fscache_acquire_cache(const char *name); -A cache can be withdrawn from the system by calling this function with a -pointer to the cache definition:: +This will look up and potentially create a cache cookie. The cache cookie may +have already been created by a network filesystem looking for it, in which case +that cache cookie will be used. If the cache cookie is not in use by another +cache, it will be moved into the preparing state, otherwise it will return +busy. - void fscache_withdraw_cache(struct fscache_cache *cache); +If successful, the cache backend can then start setting up the cache. In the +event that the initialisation fails, the cache backend should call:: -In CacheFS's case, this is called by put_super(). + void fscache_relinquish_cookie(struct fscache_cache *cache); +to reset and discard the cookie. -Security -======== -The cache methods are executed one of two contexts: +Bringing a Cache Online +======================= - (1) that of the userspace process that issued the netfs operation that caused - the cache method to be invoked, or +Once the cache is set up, it can be brought online by calling:: - (2) that of one of the processes in the FS-Cache thread pool. + int fscache_add_cache(struct fscache_cache *cache, + const struct fscache_cache_ops *ops, + void *cache_priv); -In either case, this may not be an appropriate context in which to access the -cache. +This stores the cache operations table pointer and cache private data into the +cache cookie and moves the cache to the active state, thereby allowing accesses +to take place. -The calling process's fsuid, fsgid and SELinux security identities may need to -be masqueraded for the duration of the cache driver's access to the cache. -This is left to the cache to handle; FS-Cache makes no effort in this regard. +Withdrawing a Cache From Service +================================ -Control and Statistics Presentation -=================================== +The cache backend can withdraw a cache from service by calling this function:: -The cache may present data to the outside world through FS-Cache's interfaces -in sysfs and procfs - the former for control and the latter for statistics. + void fscache_withdraw_cache(struct fscache_cache *cache); -A sysfs directory called /sys/fs/fscache/<cachetag>/ is created if CONFIG_SYSFS -is enabled. This is accessible through the kobject struct fscache_cache::kobj -and is for use by the cache as it sees fit. +This moves the cache to the withdrawn state to prevent new cache- and +volume-level accesses from starting and then waits for outstanding cache-level +accesses to complete. +The cache must then go through the data storage objects it has and tell fscache +to withdraw them, calling:: -Relevant Data Structures -======================== + void fscache_withdraw_cookie(struct fscache_cookie *cookie); - * Index/Data file FS-Cache representation cookie:: +on the cookie that each object belongs to. This schedules the specified cookie +for withdrawal. This gets offloaded to a workqueue. The cache backend can +test for completion by calling:: - struct fscache_cookie { - struct fscache_object_def *def; - struct fscache_netfs *netfs; - void *netfs_data; - ... - }; + bool fscache_are_objects_withdrawn(struct fscache_cookie *cache); - The fields that might be of use to the backend describe the object - definition, the netfs definition and the netfs's data for this cookie. - The object definition contain functions supplied by the netfs for loading - and matching index entries; these are required to provide some of the - cache operations. +Once all the cookies are withdrawn, a cache backend can withdraw all the +volumes, calling:: + void fscache_withdraw_volume(struct fscache_volume *volume); - * In-cache object representation:: +to tell fscache that a volume has been withdrawn. This waits for all +outstanding accesses on the volume to complete before returning. - struct fscache_object { - int debug_id; - enum { - FSCACHE_OBJECT_RECYCLING, - ... - } state; - spinlock_t lock - struct fscache_cache *cache; - struct fscache_cookie *cookie; - ... - }; +When the the cache is completely withdrawn, fscache should be notified by +calling:: - Structures of this type should be allocated by the cache backend and - passed to FS-Cache when requested by the appropriate cache operation. In - the case of CacheFS, they're embedded in CacheFS's internal object - structures. + void fscache_cache_relinquish(struct fscache_cache *cache); - The debug_id is a simple integer that can be used in debugging messages - that refer to a particular object. In such a case it should be printed - using "OBJ%x" to be consistent with FS-Cache. +to clear fields in the cookie and discard the caller's ref on it. - Each object contains a pointer to the cookie that represents the object it - is backing. An object should retired when put_object() is called if it is - in state FSCACHE_OBJECT_RECYCLING. The fscache_object struct should be - initialised by calling fscache_object_init(object). +Volume Cookies +============== - * FS-Cache operation record:: +Within a cache, the data storage objects are organised into logical volumes. +These are represented in the API as objects of type:: - struct fscache_operation { - atomic_t usage; - struct fscache_object *object; - unsigned long flags; - #define FSCACHE_OP_EXCLUSIVE - void (*processor)(struct fscache_operation *op); - void (*release)(struct fscache_operation *op); + struct fscache_volume { + struct fscache_cache *cache; + void *cache_priv; + unsigned int debug_id; + char *key; + unsigned int key_hash; + u64 coherency; ... }; - FS-Cache has a pool of threads that it uses to give CPU time to the - various asynchronous operations that need to be done as part of driving - the cache. These are represented by the above structure. The processor - method is called to give the op CPU time, and the release method to get - rid of it when its usage count reaches 0. +There are a number of fields here that are of interest to the caching backend: - An operation can be made exclusive upon an object by setting the - appropriate flag before enqueuing it with fscache_enqueue_operation(). If - an operation needs more processing time, it should be enqueued again. + * ``cache`` - The parent cache cookie. + * ``cache_priv`` - A place for the cache to stash private data. - * FS-Cache retrieval operation record:: + * ``debug_id`` - A debugging ID for logging in tracepoints. - struct fscache_retrieval { - struct fscache_operation op; - struct address_space *mapping; - struct list_head *to_do; - ... - }; + * ``key`` - A printable string with no '/' characters in it that represents + the index key for the volume. The key is NUL-terminated and padded out to + a multiple of 4 bytes. - A structure of this type is allocated by FS-Cache to record retrieval and - allocation requests made by the netfs. This struct is then passed to the - backend to do the operation. The backend may get extra refs to it by - calling fscache_get_retrieval() and refs may be discarded by calling - fscache_put_retrieval(). + * ``key_hash`` - A hash of the index key. This should work out the same, no + matter the cpu arch and endianness. - A retrieval operation can be used by the backend to do retrieval work. To - do this, the retrieval->op.processor method pointer should be set - appropriately by the backend and fscache_enqueue_retrieval() called to - submit it to the thread pool. CacheFiles, for example, uses this to queue - page examination when it detects PG_lock being cleared. + * ``coherency`` - A piece of coherency data that should be checked when the + volume is bound to in the cache. - The to_do field is an empty list available for the cache backend to use as - it sees fit. +Data Storage Cookies +==================== - * FS-Cache storage operation record:: +A volume is a logical group of data storage objects, each of which is +represented to the network filesystem by a cookie. Cookies are represented in +the API as objects of type:: - struct fscache_storage { - struct fscache_operation op; - pgoff_t store_limit; + struct fscache_cookie { + struct fscache_volume *volume; + void *cache_priv; + unsigned long flags; + unsigned int debug_id; + unsigned int inval_counter; + loff_t object_size; + u8 advice; + u32 key_hash; + u8 key_len; + u8 aux_len; ... }; - A structure of this type is allocated by FS-Cache to record outstanding - writes to be made. FS-Cache itself enqueues this operation and invokes - the write_page() method on the object at appropriate times to effect - storage. - - -Cache Operations -================ - -The cache backend provides FS-Cache with a table of operations that can be -performed on the denizens of the cache. These are held in a structure of type: - - :: - - struct fscache_cache_ops - - * Name of cache provider [mandatory]:: - - const char *name +The fields in the cookie that are of interest to the cache backend are: - This isn't strictly an operation, but should be pointed at a string naming - the backend. + * ``volume`` - The parent volume cookie. + * ``cache_priv`` - A place for the cache to stash private data. - * Allocate a new object [mandatory]:: + * ``flags`` - A collection of bit flags, including: - struct fscache_object *(*alloc_object)(struct fscache_cache *cache, - struct fscache_cookie *cookie) + * FSCACHE_COOKIE_NO_DATA_TO_READ - There is no data available in the + cache to be read as the cookie has been created or invalidated. - This method is used to allocate a cache object representation to back a - cookie in a particular cache. fscache_object_init() should be called on - the object to initialise it prior to returning. + * FSCACHE_COOKIE_NEEDS_UPDATE - The coherency data and/or object size has + been changed and needs committing. - This function may also be used to parse the index key to be used for - multiple lookup calls to turn it into a more convenient form. FS-Cache - will call the lookup_complete() method to allow the cache to release the - form once lookup is complete or aborted. + * FSCACHE_COOKIE_LOCAL_WRITE - The netfs's data has been modified + locally, so the cache object may be in an incoherent state with respect + to the server. + * FSCACHE_COOKIE_HAVE_DATA - The backend should set this if it + successfully stores data into the cache. - * Look up and create object [mandatory]:: + * FSCACHE_COOKIE_RETIRED - The cookie was invalidated when it was + relinquished and the cached data should be discarded. - void (*lookup_object)(struct fscache_object *object) + * ``debug_id`` - A debugging ID for logging in tracepoints. - This method is used to look up an object, given that the object is already - allocated and attached to the cookie. This should instantiate that object - in the cache if it can. + * ``inval_counter`` - The number of invalidations done on the cookie. - The method should call fscache_object_lookup_negative() as soon as - possible if it determines the object doesn't exist in the cache. If the - object is found to exist and the netfs indicates that it is valid then - fscache_obtained_object() should be called once the object is in a - position to have data stored in it. Similarly, fscache_obtained_object() - should also be called once a non-present object has been created. + * ``advice`` - Information about how the cookie is to be used. - If a lookup error occurs, fscache_object_lookup_error() should be called - to abort the lookup of that object. + * ``key_hash`` - A hash of the index key. This should work out the same, no + matter the cpu arch and endianness. + * ``key_len`` - The length of the index key. - * Release lookup data [mandatory]:: + * ``aux_len`` - The length of the coherency data buffer. - void (*lookup_complete)(struct fscache_object *object) +Each cookie has an index key, which may be stored inline to the cookie or +elsewhere. A pointer to this can be obtained by calling:: - This method is called to ask the cache to release any resources it was - using to perform a lookup. + void *fscache_get_key(struct fscache_cookie *cookie); +The index key is a binary blob, the storage for which is padded out to a +multiple of 4 bytes. - * Increment object refcount [mandatory]:: +Each cookie also has a buffer for coherency data. This may also be inline or +detached from the cookie and a pointer is obtained by calling:: - struct fscache_object *(*grab_object)(struct fscache_object *object) + void *fscache_get_aux(struct fscache_cookie *cookie); - This method is called to increment the reference count on an object. It - may fail (for instance if the cache is being withdrawn) by returning NULL. - It should return the object pointer if successful. - * Lock/Unlock object [mandatory]:: +Cookie Accounting +================= - void (*lock_object)(struct fscache_object *object) - void (*unlock_object)(struct fscache_object *object) +Data storage cookies are counted and this is used to block cache withdrawal +completion until all objects have been destroyed. The following functions are +provided to the cache to deal with that:: - These methods are used to exclusively lock an object. It must be possible - to schedule with the lock held, so a spinlock isn't sufficient. + void fscache_count_object(struct fscache_cache *cache); + void fscache_uncount_object(struct fscache_cache *cache); + void fscache_wait_for_objects(struct fscache_cache *cache); +The count function records the allocation of an object in a cache and the +uncount function records its destruction. Warning: by the time the uncount +function returns, the cache may have been destroyed. - * Pin/Unpin object [optional]:: +The wait function can be used during the withdrawal procedure to wait for +fscache to finish withdrawing all the objects in the cache. When it completes, +there will be no remaining objects referring to the cache object or any volume +objects. - int (*pin_object)(struct fscache_object *object) - void (*unpin_object)(struct fscache_object *object) - These methods are used to pin an object into the cache. Once pinned an - object cannot be reclaimed to make space. Return -ENOSPC if there's not - enough space in the cache to permit this. +Cache Management API +==================== +The cache backend implements the cache management API by providing a table of +operations that fscache can use to manage various aspects of the cache. These +are held in a structure of type:: - * Check coherency state of an object [mandatory]:: - - int (*check_consistency)(struct fscache_object *object) - - This method is called to have the cache check the saved auxiliary data of - the object against the netfs's idea of the state. 0 should be returned - if they're consistent and -ESTALE otherwise. -ENOMEM and -ERESTARTSYS - may also be returned. - - * Update object [mandatory]:: - - int (*update_object)(struct fscache_object *object) - - This is called to update the index entry for the specified object. The - new information should be in object->cookie->netfs_data. This can be - obtained by calling object->cookie->def->get_aux()/get_attr(). - - - * Invalidate data object [mandatory]:: - - int (*invalidate_object)(struct fscache_operation *op) - - This is called to invalidate a data object (as pointed to by op->object). - All the data stored for this object should be discarded and an - attr_changed operation should be performed. The caller will follow up - with an object update operation. - - fscache_op_complete() must be called on op before returning. - - - * Discard object [mandatory]:: - - void (*drop_object)(struct fscache_object *object) - - This method is called to indicate that an object has been unbound from its - cookie, and that the cache should release the object's resources and - retire it if it's in state FSCACHE_OBJECT_RECYCLING. - - This method should not attempt to release any references held by the - caller. The caller will invoke the put_object() method as appropriate. - - - * Release object reference [mandatory]:: - - void (*put_object)(struct fscache_object *object) - - This method is used to discard a reference to an object. The object may - be freed when all the references to it are released. - - - * Synchronise a cache [mandatory]:: + struct fscache_cache_ops { + const char *name; + ... + }; - void (*sync)(struct fscache_cache *cache) +This contains a printable name for the cache backend driver plus a number of +pointers to methods to allow fscache to request management of the cache: - This is called to ask the backend to synchronise a cache with its backing - device. + * Set up a volume cookie [optional]:: + void (*acquire_volume)(struct fscache_volume *volume); - * Dissociate a cache [mandatory]:: + This method is called when a volume cookie is being created. The caller + holds a cache-level access pin to prevent the cache from going away for + the duration. This method should set up the resources to access a volume + in the cache and should not return until it has done so. - void (*dissociate_pages)(struct fscache_cache *cache) + If successful, it can set ``cache_priv`` to its own data. - This is called to ask a cache to perform any page dissociations as part of - cache withdrawal. + * Clean up volume cookie [optional]:: - * Notification that the attributes on a netfs file changed [mandatory]:: + void (*free_volume)(struct fscache_volume *volume); - int (*attr_changed)(struct fscache_object *object); + This method is called when a volume cookie is being released if + ``cache_priv`` is set. - This is called to indicate to the cache that certain attributes on a netfs - file have changed (for example the maximum size a file may reach). The - cache can read these from the netfs by calling the cookie's get_attr() - method. - The cache may use the file size information to reserve space on the cache. - It should also call fscache_set_store_limit() to indicate to FS-Cache the - highest byte it's willing to store for an object. + * Look up a cookie in the cache [mandatory]:: - This method may return -ve if an error occurred or the cache object cannot - be expanded. In such a case, the object will be withdrawn from service. + bool (*lookup_cookie)(struct fscache_cookie *cookie); - This operation is run asynchronously from FS-Cache's thread pool, and - storage and retrieval operations from the netfs are excluded during the - execution of this operation. + This method is called to look up/create the resources needed to access the + data storage for a cookie. It is called from a worker thread with a + volume-level access pin in the cache to prevent it from being withdrawn. + True should be returned if successful and false otherwise. If false is + returned, the withdraw_cookie op (see below) will be called. - * Reserve cache space for an object's data [optional]:: + If lookup fails, but the object could still be created (e.g. it hasn't + been cached before), then:: - int (*reserve_space)(struct fscache_object *object, loff_t size); + void fscache_cookie_lookup_negative( + struct fscache_cookie *cookie); - This is called to request that cache space be reserved to hold the data - for an object and the metadata used to track it. Zero size should be - taken as request to cancel a reservation. + can be called to let the network filesystem proceed and start downloading + stuff whilst the cache backend gets on with the job of creating things. - This should return 0 if successful, -ENOSPC if there isn't enough space - available, or -ENOMEM or -EIO on other errors. + If successful, ``cookie->cache_priv`` can be set. - The reservation may exceed the current size of the object, thus permitting - future expansion. If the amount of space consumed by an object would - exceed the reservation, it's permitted to refuse requests to allocate - pages, but not required. An object may be pruned down to its reservation - size if larger than that already. + * Withdraw an object without any cookie access counts held [mandatory]:: - * Request page be read from cache [mandatory]:: + void (*withdraw_cookie)(struct fscache_cookie *cookie); - int (*read_or_alloc_page)(struct fscache_retrieval *op, - struct page *page, - gfp_t gfp) + This method is called to withdraw a cookie from service. It will be + called when the cookie is relinquished by the netfs, withdrawn or culled + by the cache backend or closed after a period of non-use by fscache. - This is called to attempt to read a netfs page from the cache, or to - reserve a backing block if not. FS-Cache will have done as much checking - as it can before calling, but most of the work belongs to the backend. + The caller doesn't hold any access pins, but it is called from a + non-reentrant work item to manage races between the various ways + withdrawal can occur. - If there's no page in the cache, then -ENODATA should be returned if the - backend managed to reserve a backing block; -ENOBUFS or -ENOMEM if it - didn't. + The cookie will have the ``FSCACHE_COOKIE_RETIRED`` flag set on it if the + associated data is to be removed from the cache. - If there is suitable data in the cache, then a read operation should be - queued and 0 returned. When the read finishes, fscache_end_io() should be - called. - The fscache_mark_pages_cached() should be called for the page if any cache - metadata is retained. This will indicate to the netfs that the page needs - explicit uncaching. This operation takes a pagevec, thus allowing several - pages to be marked at once. + * Change the size of a data storage object [mandatory]:: - The retrieval record pointed to by op should be retained for each page - queued and released when I/O on the page has been formally ended. - fscache_get/put_retrieval() are available for this purpose. + void (*resize_cookie)(struct netfs_cache_resources *cres, + loff_t new_size); - The retrieval record may be used to get CPU time via the FS-Cache thread - pool. If this is desired, the op->op.processor should be set to point to - the appropriate processing routine, and fscache_enqueue_retrieval() should - be called at an appropriate point to request CPU time. For instance, the - retrieval routine could be enqueued upon the completion of a disk read. - The to_do field in the retrieval record is provided to aid in this. + This method is called to inform the cache backend of a change in size of + the netfs file due to local truncation. The cache backend should make all + of the changes it needs to make before returning as this is done under the + netfs inode mutex. - If an I/O error occurs, fscache_io_error() should be called and -ENOBUFS - returned if possible or fscache_end_io() called with a suitable error - code. + The caller holds a cookie-level access pin to prevent a race with + withdrawal and the netfs must have the cookie marked in-use to prevent + garbage collection or culling from removing any resources. - fscache_put_retrieval() should be called after a page or pages are dealt - with. This will complete the operation when all pages are dealt with. + * Invalidate a data storage object [mandatory]:: - * Request pages be read from cache [mandatory]:: + bool (*invalidate_cookie)(struct fscache_cookie *cookie); - int (*read_or_alloc_pages)(struct fscache_retrieval *op, - struct list_head *pages, - unsigned *nr_pages, - gfp_t gfp) + This is called when the network filesystem detects a third-party + modification or when an O_DIRECT write is made locally. This requests + that the cache backend should throw away all the data in the cache for + this object and start afresh. It should return true if successful and + false otherwise. - This is like the read_or_alloc_page() method, except it is handed a list - of pages instead of one page. Any pages on which a read operation is - started must be added to the page cache for the specified mapping and also - to the LRU. Such pages must also be removed from the pages list and - ``*nr_pages`` decremented per page. + On entry, new I O/operations are blocked. Once the cache is in a position + to accept I/O again, the backend should release the block by calling:: - If there was an error such as -ENOMEM, then that should be returned; else - if one or more pages couldn't be read or allocated, then -ENOBUFS should - be returned; else if one or more pages couldn't be read, then -ENODATA - should be returned. If all the pages are dispatched then 0 should be - returned. + void fscache_resume_after_invalidation(struct fscache_cookie *cookie); + If the method returns false, caching will be withdrawn for this cookie. - * Request page be allocated in the cache [mandatory]:: - int (*allocate_page)(struct fscache_retrieval *op, - struct page *page, - gfp_t gfp) + * Prepare to make local modifications to the cache [mandatory]:: - This is like the read_or_alloc_page() method, except that it shouldn't - read from the cache, even if there's data there that could be retrieved. - It should, however, set up any internal metadata required such that - the write_page() method can write to the cache. + void (*prepare_to_write)(struct fscache_cookie *cookie); - If there's no backing block available, then -ENOBUFS should be returned - (or -ENOMEM if there were other problems). If a block is successfully - allocated, then the netfs page should be marked and 0 returned. + This method is called when the network filesystem finds that it is going + to need to modify the contents of the cache due to local writes or + truncations. This gives the cache a chance to note that a cache object + may be incoherent with respect to the server and may need writing back + later. This may also cause the cached data to be scrapped on later + rebinding if not properly committed. - * Request pages be allocated in the cache [mandatory]:: + * Begin an operation for the netfs lib [mandatory]:: - int (*allocate_pages)(struct fscache_retrieval *op, - struct list_head *pages, - unsigned *nr_pages, - gfp_t gfp) + bool (*begin_operation)(struct netfs_cache_resources *cres, + enum fscache_want_state want_state); - This is an multiple page version of the allocate_page() method. pages and - nr_pages should be treated as for the read_or_alloc_pages() method. + This method is called when an I/O operation is being set up (read, write + or resize). The caller holds an access pin on the cookie and must have + marked the cookie as in-use. + If it can, the backend should attach any resources it needs to keep around + to the netfs_cache_resources object and return true. - * Request page be written to cache [mandatory]:: + If it can't complete the setup, it should return false. - int (*write_page)(struct fscache_storage *op, - struct page *page); + The want_state parameter indicates the state the caller needs the cache + object to be in and what it wants to do during the operation: - This is called to write from a page on which there was a previously - successful read_or_alloc_page() call or similar. FS-Cache filters out - pages that don't have mappings. + * ``FSCACHE_WANT_PARAMS`` - The caller just wants to access cache + object parameters; it doesn't need to do data I/O yet. - This method is called asynchronously from the FS-Cache thread pool. It is - not required to actually store anything, provided -ENODATA is then - returned to the next read of this page. + * ``FSCACHE_WANT_READ`` - The caller wants to read data. - If an error occurred, then a negative error code should be returned, - otherwise zero should be returned. FS-Cache will take appropriate action - in response to an error, such as withdrawing this object. + * ``FSCACHE_WANT_WRITE`` - The caller wants to write to or resize the + cache object. - If this method returns success then FS-Cache will inform the netfs - appropriately. + Note that there won't necessarily be anything attached to the cookie's + cache_priv yet if the cookie is still being created. - * Discard retained per-page metadata [mandatory]:: +Data I/O API +============ - void (*uncache_page)(struct fscache_object *object, struct page *page) +A cache backend provides a data I/O API by through the netfs library's ``struct +netfs_cache_ops`` attached to a ``struct netfs_cache_resources`` by the +``begin_operation`` method described above. - This is called when a netfs page is being evicted from the pagecache. The - cache backend should tear down any internal representation or tracking it - maintains for this page. +See the Documentation/filesystems/netfs_library.rst for a description. -FS-Cache Utilities -================== +Miscellaneous Functions +======================= FS-Cache provides some utilities that a cache backend may make use of: * Note occurrence of an I/O error in a cache:: - void fscache_io_error(struct fscache_cache *cache) + void fscache_io_error(struct fscache_cache *cache); - This tells FS-Cache that an I/O error occurred in the cache. After this - has been called, only resource dissociation operations (object and page - release) will be passed from the netfs to the cache backend for the - specified cache. + This tells FS-Cache that an I/O error occurred in the cache. This + prevents any new I/O from being started on the cache. This does not actually withdraw the cache. That must be done separately. + * Note cessation of caching on a cookie due to failure:: - * Invoke the retrieval I/O completion function:: - - void fscache_end_io(struct fscache_retrieval *op, struct page *page, - int error); - - This is called to note the end of an attempt to retrieve a page. The - error value should be 0 if successful and an error otherwise. - - - * Record that one or more pages being retrieved or allocated have been dealt - with:: - - void fscache_retrieval_complete(struct fscache_retrieval *op, - int n_pages); - - This is called to record the fact that one or more pages have been dealt - with and are no longer the concern of this operation. When the number of - pages remaining in the operation reaches 0, the operation will be - completed. - - - * Record operation completion:: - - void fscache_op_complete(struct fscache_operation *op); - - This is called to record the completion of an operation. This deducts - this operation from the parent object's run state, potentially permitting - one or more pending operations to start running. - - - * Set highest store limit:: - - void fscache_set_store_limit(struct fscache_object *object, - loff_t i_size); - - This sets the limit FS-Cache imposes on the highest byte it's willing to - try and store for a netfs. Any page over this limit is automatically - rejected by fscache_read_alloc_page() and co with -ENOBUFS. - - - * Mark pages as being cached:: - - void fscache_mark_pages_cached(struct fscache_retrieval *op, - struct pagevec *pagevec); - - This marks a set of pages as being cached. After this has been called, - the netfs must call fscache_uncache_page() to unmark the pages. - - - * Perform coherency check on an object:: - - enum fscache_checkaux fscache_check_aux(struct fscache_object *object, - const void *data, - uint16_t datalen); - - This asks the netfs to perform a coherency check on an object that has - just been looked up. The cookie attached to the object will determine the - netfs to use. data and datalen should specify where the auxiliary data - retrieved from the cache can be found. - - One of three values will be returned: - - FSCACHE_CHECKAUX_OKAY - The coherency data indicates the object is valid as is. - - FSCACHE_CHECKAUX_NEEDS_UPDATE - The coherency data needs updating, but otherwise the object is - valid. - - FSCACHE_CHECKAUX_OBSOLETE - The coherency data indicates that the object is obsolete and should - be discarded. - - - * Initialise a freshly allocated object:: - - void fscache_object_init(struct fscache_object *object); - - This initialises all the fields in an object representation. - - - * Indicate the destruction of an object:: - - void fscache_object_destroyed(struct fscache_cache *cache); - - This must be called to inform FS-Cache that an object that belonged to a - cache has been destroyed and deallocated. This will allow continuation - of the cache withdrawal process when it is stopped pending destruction of - all the objects. - - - * Indicate negative lookup on an object:: - - void fscache_object_lookup_negative(struct fscache_object *object); - - This is called to indicate to FS-Cache that a lookup process for an object - found a negative result. - - This changes the state of an object to permit reads pending on lookup - completion to go off and start fetching data from the netfs server as it's - known at this point that there can't be any data in the cache. - - This may be called multiple times on an object. Only the first call is - significant - all subsequent calls are ignored. - - - * Indicate an object has been obtained:: - - void fscache_obtained_object(struct fscache_object *object); - - This is called to indicate to FS-Cache that a lookup process for an object - produced a positive result, or that an object was created. This should - only be called once for any particular object. - - This changes the state of an object to indicate: - - (1) if no call to fscache_object_lookup_negative() has been made on - this object, that there may be data available, and that reads can - now go and look for it; and - - (2) that writes may now proceed against this object. - - - * Indicate that object lookup failed:: - - void fscache_object_lookup_error(struct fscache_object *object); - - This marks an object as having encountered a fatal error (usually EIO) - and causes it to move into a state whereby it will be withdrawn as soon - as possible. - - - * Indicate that a stale object was found and discarded:: - - void fscache_object_retrying_stale(struct fscache_object *object); - - This is called to indicate that the lookup procedure found an object in - the cache that the netfs decided was stale. The object has been - discarded from the cache and the lookup will be performed again. - - - * Indicate that the caching backend killed an object:: - - void fscache_object_mark_killed(struct fscache_object *object, - enum fscache_why_object_killed why); - - This is called to indicate that the cache backend preemptively killed an - object. The why parameter should be set to indicate the reason: + void fscache_caching_failed(struct fscache_cookie *cookie); - FSCACHE_OBJECT_IS_STALE - - the object was stale and needs discarding. + This notes that a the caching that was being done on a cookie failed in + some way, for instance the backing storage failed to be created or + invalidation failed and that no further I/O operations should take place + on it until the cache is reset. - FSCACHE_OBJECT_NO_SPACE - - there was insufficient cache space + * Count I/O requests:: - FSCACHE_OBJECT_WAS_RETIRED - - the object was retired when relinquished. + void fscache_count_read(void); + void fscache_count_write(void); - FSCACHE_OBJECT_WAS_CULLED - - the object was culled to make space. + These record reads and writes from/to the cache. The numbers are + displayed in /proc/fs/fscache/stats. + * Count out-of-space errors:: - * Get and release references on a retrieval record:: + void fscache_count_no_write_space(void); + void fscache_count_no_create_space(void); - void fscache_get_retrieval(struct fscache_retrieval *op); - void fscache_put_retrieval(struct fscache_retrieval *op); + These record ENOSPC errors in the cache, divided into failures of data + writes and failures of filesystem object creations (e.g. mkdir). - These two functions are used to retain a retrieval record while doing - asynchronous data retrieval and block allocation. + * Count objects culled:: + void fscache_count_culled(void); - * Enqueue a retrieval record for processing:: + This records the culling of an object. - void fscache_enqueue_retrieval(struct fscache_retrieval *op); + * Get the cookie from a set of cache resources:: - This enqueues a retrieval record for processing by the FS-Cache thread - pool. One of the threads in the pool will invoke the retrieval record's - op->op.processor callback function. This function may be called from - within the callback function. + struct fscache_cookie *fscache_cres_cookie(struct netfs_cache_resources *cres) + Pull a pointer to the cookie from the cache resources. This may return a + NULL cookie if no cookie was set. - * List of object state names:: - const char *fscache_object_states[]; +API Function Reference +====================== - For debugging purposes, this may be used to turn the state that an object - is in into a text string for display purposes. +.. kernel-doc:: include/linux/fscache-cache.h
Documentation/filesystems/caching/cachefiles.rst +3 -3
@@ -1,8 +1,8 @@ .. SPDX-License-Identifier: GPL-2.0 -=============================================== -CacheFiles: CACHE ON ALREADY MOUNTED FILESYSTEM -=============================================== +=================================== +Cache on Already Mounted Filesystem +=================================== .. Contents:
Documentation/filesystems/caching/fscache.rst +154 -371
640 lines not on this page · raw message
Documentation/filesystems/caching/index.rst +1 -3
@@ -7,8 +7,6 @@ Filesystem Caching :maxdepth: 2 fscache - object + netfs-api backend-api cachefiles - netfs-api - operations
Documentation/filesystems/caching/netfs-api.rst +317 -766
1,213 lines not on this page · raw message
Documentation/filesystems/caching/object.rst deleted +0 -313
102 lines not on this page · raw message
Documentation/filesystems/caching/operations.rst deleted +0 -210
210 lines not on this page · raw message
Documentation/filesystems/netfs_library.rst +10 -6
Lines of this file are not shown. View the raw message.
The rest of this diff is not shown. View the raw message.
Part of the diff is not shown here. View the raw message.
Quoted 456 lines · David Howells
Here's a set of patches implements a rewrite of the fscache driver and a matching rewrite of the cachefiles driver, significantly simplifying the code compared to what's upstream, removing the complex operation scheduling and object state machine in favour of something much smaller and simpler.
The patchset is structured such that the first few patches disable fscache use by the network filesystems using it, remove the cachefiles driver entirely and as much of the fscache driver as can be got away with without causing build failures in the network filesystems. The patches after that recreate fscache and then cachefiles, attempting to add the pieces in a logical order. Finally, the filesystems are reenabled and then the very last patch changes the documentation.
WHY REWRITE?
============Fscache's operation scheduling API was intended to handle sequencing of cache operations, which were all required (where possible) to run asynchronously in parallel with the operations being done by the network filesystem, whilst allowing the cache to be brought online and offline and to interrupt service for invalidation.
With the advent of the tmpfile capacity in the VFS, however, an opportunity arises to do invalidation much more simply, without having to wait for I/O that's actually in progress: Cachefiles can simply create a tmpfile, cut over the file pointer for the backing object attached to a cookie and abandon the in-progress I/O, dismissing it upon completion.
Future work here would involve using Omar Sandoval's
vfs_link()with AT_LINK_REPLACE[1] to allow an extant file to be displaced by a new hard link from a tmpfile as currently I have to unlink the old file first.These patches can also simplify the object state handling as I/O operations to the cache don't all have to be brought to a stop in order to invalidate a file. To that end, and with an eye on to writing a new backing cache model in the future, I've taken the opportunity to simplify the indexing structure.
I've separated the index cookie concept from the file cookie concept by C type now. The former is now called a "volume cookie" (struct
fscache_volume) and there is a container of file cookies. There are then just the two levels. All the index cookie levels are collapsed into a single volume cookie, and this has a single printable string as a key. For instance, an AFS volume would have a key of something like "afs,example.com,1000555", combining the filesystem name, cell name and volume ID. This is freeform, but must not have '/' chars in it.I've also eliminated all pointers back from fscache into the network filesystem. This required the duplication of a little bit of data in the cookie (cookie key, coherency data and file size), but it's not actually that much. This gets rid of problems with making sure we keep netfs data structures around so that the cache can access them.
These patches mean that most of the code that was in the drivers before is simply gone and those drivers are now almost entirely new code. That being the case, there doesn't seem any particular reason to try and maintain bisectability across it. Further, there has to be a point in the middle where things are cut over as there's a single point everything has to go through (ie.
/dev/cachefiles) and it can't be in use by two drivers at once.ISSUES YET OUTSTANDING
======================There are some issues still outstanding, unaddressed by this patchset, that will need fixing in future patchsets, but that don't stop this series from being usable:
(1) The cachefiles driver needs to stop using the backing filesystem's metadata to store information about what parts of the cache are populated. This is not reliable with modern extent-based filesystems.
Fixing this is deferred to a separate patchset as it involves negotiation with the network filesystem and the VM as to how much data to download to fulfil a read - which brings me on to (2)...
(2) NFS and CIFS do not take account of how the cache would like I/O to be structured to meet its granularity requirements. Previously, the cache used page granularity, which was fine as the network filesystems also dealt in page granularity, and the backing filesystem (ext4, xfs or whatever) did whatever it did out of sight. However, we now have folios to deal with and the cache will now have to store its own metadata to track its contents.
The change I'm looking at making for cachefiles is to store content bitmaps in one or more xattrs and making a bit in the map correspond to something like a 256KiB block. However, the size of an xattr and the fact that they have to be read/updated in one go means that I'm looking at covering 1GiB of data per 512-byte map and storing each map in an xattr. Cachefiles has the potential to grow into a fully fledged filesystem of its very own if I'm not careful.
However, I'm also looking at changing things even more radically and going to a different model of how the cache is arranged and managed - one that's more akin to the way, say, openafs does things - which brings me on to (3)...
(3) The way cachefilesd does culling is very inefficient for large caches and it would be better to move it into the kernel if I can as cachefilesd has to keep asking the kernel if it can cull a file. Changing the way the backend works would allow this to be addressed.
BITS THAT MAY BE CONTROVERSIAL
==============================There are some bits I've added that may be controversial:
(1) I've provided a flag,
S_KERNEL_FILE, that cachefiles uses to check if a files is already being used by some other kernel service (e.g. a duplicate cachefiles cache in the same directory) and reject it if it is. This isn't entirely necessary, but it helps prevent accidental data corruption.I don't want to use
S_SWAPFILEas that has other effects, but quite possiblyswapon()should setS_KERNEL_FILEtoo.Note that it doesn't prevent userspace from interfering, though perhaps it should. (I have made it prevent a marked directory from being rmdir-able).
(2) Cachefiles wants to keep the backing file for a cookie open whilst we might need to write to it from network filesystem writeback. The problem is that the network filesystem unuses its cookie when its file is closed, and so we have nothing pinning the cachefiles file open and it will get closed automatically after a short time to avoid EMFILE/ENFILE problems.
Reopening the cache file, however, is a problem if this is being done due to writeback triggered by
exit(). Some filesystems will oops if we try to open a file in that context because they want to accesscurrent->fsor suchlike.To get around this, I added the following:
(A) An inode flag,
I_PINNING_FSCACHE_WB, to be set on a network filesystem inode to indicate that we have a usage count on the cookie caching that inode.(B) A flag in struct
writeback_control,unpinned_fscache_wb, that is set when__writeback_single_inode()clears the last dirty page fromi_pages- at which point it clearsI_PINNING_FSCACHE_WBand sets this flag.This has to be done here so that clearing
I_PINNING_FSCACHE_WBcan be done atomically with the check ofPAGECACHE_TAG_DIRTYthat clearsI_DIRTY_PAGES.(C) A function,
fscache_set_page_dirty(), which if it is not set, setsI_PINNING_FSCACHE_WBand callsfscache_use_cookie()to pin the cache resources.(D) A function,
fscache_unpin_writeback(), to be called by ->write_inode() to unuse the cookie.(E) A function,
fscache_clear_inode_writeback(), to be called when the inode is evicted, beforeclear_inode()is called. This cleans up any lingeringI_PINNING_FSCACHE_WB.The network filesystem can then use these tools to make sure that
fscache_write_to_cache()can write locally modified data to the cache as well as to the server.For the future, I'm working on write helpers for netfs lib that should allow this facility to be removed by keeping track of the dirty regions separately - but that's incomplete at the moment and is also going to be affected by folios, one way or another, since it deals with pages.
These patches can be found also on:
https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs.git/log/?h=fscache-rewrite
David
Changes
=======
ver #2:
- Fix an unused-var warning due toCONFIG_9P_FSCACHE=n.
- Usegfpflags_allow_blocking()rather than using flag directly.
- Fixed some error logging in a couple of cachefiles functions.
- Fixed an error check in the fscache volume allocation.
- Need to unmark an inode we've moved to the graveyard before unlocking.
- Upgraded to -rc4 to allow for upstream changes to cifs.
- Should only change to inval state if can get access to cache.
- Don't holdn_accesseselevated whilst cache is bound to a cookie, but rather add a flag that prevents the state machine from being queued whenn_accessesreaches 0.
- Remove the unused cookie pointer field from thefscache_acquiretracepoint.
- Added missing transition toLRU_DISCARDINGstate.
- Added two ceph patches from Jeff Layton[2].
- RemoveNFS_INO_FSCACHEas it's no longer used.
- In NFS, need to unuse a cookie on file-release, not inode-clear.
- Filled in the NFS cache I/O routines, borrowing from the previously posted fallback I/O code[3].References
==========These patches have been published for review before, firstly as part of a larger set:
Link https://lore.kernel.org/r/159465766378.1376105.11619976251039287525.stgit@warthog.procyon.org.uk/ open hereThen as a cut-down set:
Link https://lore.kernel.org/r/161118128472.1232039.11746799833066425131.stgit@warthog.procyon.org.uk/ # v1 open hereLink https://lore.kernel.org/r/161161025063.2537118.2009249444682241405.stgit@warthog.procyon.org.uk/ # v2 open hereLink https://lore.kernel.org/r/161340385320.1303470.2392622971006879777.stgit@warthog.procyon.org.uk/ # v3 open hereI split out a set to just restructure the I/O, which got merged back in to this one:
Link https://lore.kernel.org/r/163363935000.1980952.15279841414072653108.stgit@warthog.procyon.org.uk/ open hereLink https://lore.kernel.org/r/163189104510.2509237.10805032055807259087.stgit@warthog.procyon.org.uk/ # v2 open here... and a larger set to do the conversion, also merged back into this one:
Older versions of this one:
Proposals/information about the design have been published here:
And requests for information:
I've posted partial patches to try and help 9p and cifs along:
---
Dave Wysochanski (1):
nfs: Convert to new fscache volume/cookie APIDavid Howells (64):
fscache, cachefiles: Disable configuration
cachefiles: Delete the cachefiles driver pending rewrite
fscache: Remove the contents of the fscache driver, pending rewrite
netfs: Display the netfs inode number in thenetfs_readtracepoint
netfs: Pass a flag to ->prepare_write() to say if there's no alloc'd space
fscache: Introduce new driver
fscache: Implement a hash function
fscache: Implement cache registration
fscache: Implement volume registration
fscache: Implement cookie registration
fscache: Implement cache-level access helpers
fscache: Implement volume-level access helpers
fscache: Implement cookie-level access helpers
fscache: Implement functions add/remove a cache
fscache: Provide and use cache methods to lookup/create/free a volume
fscache: Add a function for a cache backend to note an I/O error
fscache: Implement simple cookie state machine
fscache: Implement cookie user counting and resource pinning
fscache: Implement cookie invalidation
fscache: Provide a means to begin an operation
fscache: Count data storage objects in a cache
fscache: Provide read/write stat counters for the cache
fscache: Provide a function to let the netfs update its coherency data
netfs: Pass more information on how to deal with a hole in the cache
fscache: Implement raw I/O interface
fscache: Implement higher-level write I/O interface
vfs, fscache: Implement pinning of cache usage for writeback
fscache: Provide a function to note the release of a page
fscache: Provide a function to resize a cookie
cachefiles: Introduce rewritten driver
cachefiles: Define structs
cachefiles: Add some error injection support
cachefiles: Add a couple of tracepoints for logging errors
cachefiles: Add cache error reporting macro
cachefiles: Add security derivation
cachefiles: Register a miscdev and parse commands over it
cachefiles: Provide a function to check how much space there is vfs, cachefiles: Mark a backing file in use with an inode flag
cachefiles: Implement a function to get/create a directory in the cache
cachefiles: Implement cache registration and withdrawal
cachefiles: Implement volume support
cachefiles: Add tracepoints for calls to the VFS
cachefiles: Implement object lifecycle funcs
cachefiles: Implement key to filename encoding
cachefiles: Implement metadata/coherency data storage in xattrs
cachefiles: Mark a backing file in use with an inode flag
cachefiles: Implement culling daemon commands
cachefiles: Implement backing file wrangling
cachefiles: Implement begin and end I/O operation
cachefiles: Implement cookie resize for truncate
cachefiles: Implement the I/O routines
cachefiles: Allow cachefiles to actually function
fscache, cachefiles: Display stats of no-space events fscache, cachefiles: Display stat of culling events
afs: Handle len being extending over page end in write_begin/write_end
afs: Fixafs_write_end()to handle len > page size
afs: Convert afs to use the new fscache API
afs: Copy local writes to the cache when writing to the server
afs: Skip truncation on the server of data we haven't written yet 9p: Use fscache indexing rewrite and reenable caching
9p: Copy local writes to the cache when writing to the server
nfs: Implement cache I/O by accessing the cache directly
cifs: Support fscache indexing rewrite (untested)
fscache: Rewrite documentationJeff Layton (2):
ceph: conversion to new fscache API
ceph: add fscache writeback support.../filesystems/caching/backend-api.rst | 847 ++++------ .../filesystems/caching/cachefiles.rst | 6 +- Documentation/filesystems/caching/fscache.rst | 525 ++---- Documentation/filesystems/caching/index.rst | 4 +- .../filesystems/caching/netfs-api.rst | 1083 ++++--------- Documentation/filesystems/caching/object.rst | 313 ---- .../filesystems/caching/operations.rst | 210 --- Documentation/filesystems/netfs_library.rst | 16 +- fs/9p/Kconfig | 2 +- fs/9p/cache.c | 193 +-- fs/9p/cache.h | 25 +- fs/9p/v9fs.c | 17 +- fs/9p/v9fs.h | 13 +- fs/9p/vfs_addr.c | 54 +- fs/9p/vfs_dir.c | 11 + fs/9p/vfs_file.c | 3 +- fs/9p/vfs_inode.c | 24 +- fs/9p/vfs_inode_dotl.c | 3 +- fs/9p/vfs_super.c | 3 + fs/afs/Kconfig | 2 +- fs/afs/Makefile | 3 - fs/afs/cache.c | 68 - fs/afs/cell.c | 12 - fs/afs/file.c | 37 +- fs/afs/inode.c | 101 +- fs/afs/internal.h | 37 +- fs/afs/main.c | 14 - fs/afs/super.c | 1 + fs/afs/volume.c | 29 +- fs/afs/write.c | 100 +- fs/cachefiles/Kconfig | 7 + fs/cachefiles/Makefile | 6 +- fs/cachefiles/bind.c | 278 ---- fs/cachefiles/cache.c | 378 +++++ fs/cachefiles/daemon.c | 180 +-- fs/cachefiles/error_inject.c | 46 + fs/cachefiles/interface.c | 747 ++++----- fs/cachefiles/internal.h | 265 ++-- fs/cachefiles/io.c | 330 ++-- fs/cachefiles/key.c | 201 ++- fs/cachefiles/main.c | 22 +- fs/cachefiles/namei.c | 1221 ++++++-------- fs/cachefiles/rdwr.c | 972 ------------ fs/cachefiles/security.c | 2 +- fs/cachefiles/volume.c | 118 ++ fs/cachefiles/xattr.c | 369 ++--- fs/ceph/Kconfig | 2 +- fs/ceph/addr.c | 101 +- fs/ceph/cache.c | 218 +-- fs/ceph/cache.h | 97 +- fs/ceph/caps.c | 3 +- fs/ceph/file.c | 13 +- fs/ceph/inode.c | 22 +- fs/ceph/super.c | 10 +- fs/ceph/super.h | 3 +- fs/cifs/Kconfig | 2 +- fs/cifs/Makefile | 2 +- fs/cifs/cache.c | 105 -- fs/cifs/cifsfs.c | 11 +- fs/cifs/cifsglob.h | 5 +- fs/cifs/connect.c | 12 - fs/cifs/file.c | 64 +- fs/cifs/fscache.c | 319 +--- fs/cifs/fscache.h | 106 +- fs/cifs/inode.c | 36 +- fs/fs-writeback.c | 8 + fs/fscache/Makefile | 6 +- fs/fscache/cache.c | 618 ++++---- fs/fscache/cookie.c | 1402 +++++++++-------- fs/fscache/fsdef.c | 98 -- fs/fscache/internal.h | 315 +--- fs/fscache/io.c | 376 ++++- fs/fscache/main.c | 136 +- fs/fscache/netfs.c | 74 - fs/fscache/object.c | 1125 ------------- fs/fscache/operation.c | 633 -------- fs/fscache/page.c | 1242 --------------- fs/fscache/proc.c | 45 +- fs/fscache/stats.c | 293 +--- fs/fscache/volume.c | 508 ++++++ fs/namei.c | 3 +- fs/netfs/read_helper.c | 10 +- fs/nfs/Kconfig | 2 +- fs/nfs/Makefile | 2 +- fs/nfs/client.c | 4 - fs/nfs/direct.c | 2 + fs/nfs/file.c | 13 +- fs/nfs/fscache-index.c | 140 -- fs/nfs/fscache.c | 490 ++---- fs/nfs/fscache.h | 182 +-- fs/nfs/inode.c | 11 +- fs/nfs/nfstrace.h | 1 - fs/nfs/read.c | 25 +- fs/nfs/super.c | 28 +- fs/nfs/write.c | 8 +- include/linux/fs.h | 4 + include/linux/fscache-cache.h | 614 ++------ include/linux/fscache.h | 1015 +++++------- include/linux/netfs.h | 15 +- include/linux/nfs_fs.h | 1 - include/linux/nfs_fs_sb.h | 9 +- include/linux/writeback.h | 1 + include/trace/events/cachefiles.h | 487 ++++-- include/trace/events/fscache.h | 626 ++++---- include/trace/events/netfs.h | 5 +- 105 files changed, 7121 insertions(+), 13485 deletions(-) delete mode 100644 Documentation/filesystems/caching/object.rst delete mode 100644 Documentation/filesystems/caching/operations.rst delete mode 100644 fs/afs/cache.c delete mode 100644 fs/cachefiles/bind.c create mode 100644 fs/cachefiles/cache.c create mode 100644 fs/cachefiles/error_inject.c delete mode 100644 fs/cachefiles/rdwr.c create mode 100644 fs/cachefiles/volume.c
Quoted 10 lines
delete mode 100644 fs/cifs/cache.c delete mode 100644 fs/fscache/fsdef.c delete mode 100644 fs/fscache/netfs.c delete mode 100644 fs/fscache/object.c delete mode 100644 fs/fscache/operation.c delete mode 100644 fs/fscache/page.c create mode 100644 fs/fscache/volume.c delete mode 100644 fs/nfs/fscache-index.c
Testing this with NFS and fscache enabled.
- fscache unit tests: PASS
- xfstests NFSv4.2 (rhel8 server): PASS
- xfstests NFSv4.1 (netapp server): PASS
- xfstests NFSv4.0 (netapp server): PASS
- xfstests NFSv3 (rhel8 server): FAIL (see below use after free w/kasan)
NOTE: I had one patch that converted nfs fscache dfprintk's dfprintks to trace events on top of your series, but tracepoints were not enabled, and I don't think my patch was a contributor to this kasan use-after-free. Unfortunately after I rebuilt it, I did not reproduce the problem so far. I wonder if there is a race with nfs_fscache_open_file(), fscache_use_cookie() and then fscache_invalidate(), but I've not read through this enough to map out a possible theory. Maybe you can spot it faster than me.
[ 405.242590] run fstests generic/011 at 2021-12-10 11:44:26^M [ 432.920087] ==================================================================^M [ 432.921382] BUG: KASAN: use-after-free in fscache_unhash_cookie+0x9e/0x160 [fscache]^M [ 432.922617] Write of size 8 at addr ffff88812c185200 by task kworker/u16:179/8137^M [ 432.923795] ^M [ 432.924059] CPU: 0 PID: 8137 Comm: kworker/u16:179 Kdump: loaded Not tainted 5.16.0-rc4-fscache-rewrite-trace-kasan+ #13^M [ 432.925737] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.14.0-4.fc34 04/01/2014^M [ 432.927057] Workqueue: fscache fscache_cookie_worker [fscache]^M [ 432.928035] Call Trace:^M [ 432.928467] <TASK>^M [ 432.928844] dump_stack_lvl+0x48/0x5e^M [ 432.929447] print_address_description.constprop.0+0x1f/0x140^M [ 432.930363] ? fscache_unhash_cookie+0x9e/0x160 [fscache]^M [ 432.931240] kasan_report.cold+0x7f/0x11b^M [ 432.931891] ? fscache_unhash_cookie+0x9e/0x160 [fscache]^M [ 432.932773] fscache_unhash_cookie+0x9e/0x160 [fscache]^M [ 432.933626] fscache_cookie_worker+0x1f0/0xad0 [fscache]^M [ 432.934477] ? _raw_spin_unlock_bh+0x20/0x20^M [ 432.935180] ? __list_add_valid+0x2f/0x60^M [ 432.935820] process_one_work+0x3d2/0x710^M [ 432.936702] worker_thread+0x2d2/0x6e0^M [ 432.937475] ? process_one_work+0x710/0x710^M [ 432.938177] kthread+0x223/0x260^M [ 432.938711] ? set_kthread_struct+0x80/0x80^M [ 432.939395] ret_from_fork+0x22/0x30^M [ 432.940010] </TASK>^M [ 432.940388] ^M [ 432.940672] Allocated by task 9139:^M [ 432.941253] kasan_save_stack+0x1e/0x50^M [ 432.941888] __kasan_slab_alloc+0x66/0x80^M [ 432.942553] kmem_cache_alloc+0x147/0x2c0^M [ 432.943233] __fscache_acquire_cookie+0xa1/0x9b0 [fscache]^M [ 432.944125] nfs_fscache_init_inode+0x2dc/0x340 [nfs]^M [ 432.945038] nfs_fhget+0x757/0xcd0 [nfs]^M [ 432.945745] nfs_add_or_obtain+0x163/0x190 [nfs]^M [ 432.946563] nfs3_proc_create+0x1e0/0x4f0 [nfsv3]^M [ 432.947324] nfs_create+0x106/0x270 [nfs]^M [ 432.948054] path_openat+0x14ec/0x1810^M [ 432.948679] do_filp_open+0x131/0x230^M [ 432.949281] do_sys_openat2+0xe4/0x240^M [ 432.949903] __x64_sys_creat+0x99/0xb0^M [ 432.949903] __x64_sys_creat+0x99/0xb0^M [ 432.950520] do_syscall_64+0x3b/0x90^M [ 432.951113] entry_SYSCALL_64_after_hwframe+0x44/0xae^M [ 432.951932] ^M [ 432.952207] Freed by task 8191:^M [ 432.952727] kasan_save_stack+0x1e/0x50^M [ 432.953369] kasan_set_track+0x21/0x30^M [ 432.953969] kasan_set_free_info+0x20/0x30^M [ 432.954626] __kasan_slab_free+0xec/0x120^M [ 432.955288] slab_free_freelist_hook+0x66/0x130^M [ 432.956002] kmem_cache_free+0x108/0x400^M [ 432.956640] fscache_put_cookie+0x10f/0x150 [fscache]^M [ 432.957454] process_one_work+0x3d2/0x710^M [ 432.958111] worker_thread+0x2d2/0x6e0^M [ 432.958761] kthread+0x223/0x260^M [ 432.959298] ret_from_fork+0x22/0x30^M [ 432.959884] ^M [ 432.960151] Last potentially related work creation:^M [ 432.960914] kasan_save_stack+0x1e/0x50^M [ 432.961531] __kasan_record_aux_stack+0xae/0xc0^M [ 432.962255] insert_work+0x34/0x190^M [ 432.962825] __queue_work+0x336/0x680^M [ 432.963412] queue_work_on+0x60/0x70^M [ 432.963983] __fscache_withdraw_cookie+0xab/0x160 [fscache]^M [ 432.964860] fscache_cookie_lru_worker+0x227/0x2f0 [fscache]^M [ 432.965746] process_one_work+0x3d2/0x710^M [ 432.966394] worker_thread+0x2d2/0x6e0^M [ 432.966982] kthread+0x223/0x260^M [ 432.967515] ret_from_fork+0x22/0x30^M [ 432.968088] ^M [ 432.968354] Second to last potentially related work creation:^M [ 432.969236] kasan_save_stack+0x1e/0x50^M [ 432.969843] __kasan_record_aux_stack+0xae/0xc0^M [ 432.970554] insert_work+0x34/0x190^M [ 432.971109] __queue_work+0x336/0x680^M [ 432.971688] queue_work_on+0x60/0x70^M [ 432.972252] __fscache_use_cookie+0x25b/0x370 [fscache]^M [ 432.973083] nfs_fscache_open_file+0xb0/0x230 [nfs]^M [ 432.973918] nfs_open+0x7a/0xc0 [nfs]^M [ 432.974592] do_dentry_open+0x28c/0x690^M [ 432.975203] path_openat+0x1139/0x1810^M [ 432.975796] do_filp_open+0x131/0x230^M [ 432.976374] do_sys_openat2+0xe4/0x240^M [ 432.976978] __x64_sys_creat+0x99/0xb0^M [ 432.977573] do_syscall_64+0x3b/0x90^M [ 432.978147] entry_SYSCALL_64_after_hwframe+0x44/0xae^M [ 432.978927] ^M [ 432.979191] The buggy address belongs to the object at ffff88812c1851d0^M [ 432.979191] which belongs to the cache fscache_cookie_jar of size 176^M [ 432.981138] The buggy address is located 48 bytes inside of^M [ 432.981138] 176-byte region [ffff88812c1851d0, ffff88812c185280)^M [ 432.982859] The buggy address belongs to the page:^M [ 432.983604] page:00000000200db521 refcount:1 mapcount:0 mapping:0000000000000000 index:0xffff88812c1852c0 pfn:0x12c184^M [ 432.985230] head:00000000200db521 order:1 compound_mapcount:0^M [ 432.986102] flags: 0x17ffffc0010200(slab|head|node=0|zone=2|lastcpupid=0x1fffff)^M [ 432.987245] raw: 0017ffffc0010200 ffffea00042d6f00 dead000000000004 ffff888110b8d180^M [ 432.988413] raw: ffff88812c1852c0 0000000080220020 00000001ffffffff 0000000000000000^M [ 432.989589] page dumped because: kasan: bad access detected^M [ 432.990470] ^M [ 432.990751] Memory state around the buggy address:^M [ 432.991487] ffff88812c185100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb^M [ 432.992586] ffff88812c185180: fb fb fc fc fc fc fc fc fc fc fa fb fb fb fb fb^M [ 432.993696] >ffff88812c185200: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb^M [ 432.994803] ^^M [ 432.995330] ffff88812c185280: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb^M [ 432.996439] ffff88812c185300: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fc fc^M [ 432.997536] ==================================================================^M [ 432.998622] Disabling lock debugging due to kernel taint^M [ 452.411206] run fstests generic/012 at 2021-12-10 11:45:14^M
Quoted 460 lines · David Wysochanski
Quoted 458 lines · David Howells
Here's a set of patches implements a rewrite of the fscache driver and a matching rewrite of the cachefiles driver, significantly simplifying the code compared to what's upstream, removing the complex operation scheduling and object state machine in favour of something much smaller and simpler.
The patchset is structured such that the first few patches disable fscache use by the network filesystems using it, remove the cachefiles driver entirely and as much of the fscache driver as can be got away with without causing build failures in the network filesystems. The patches after that recreate fscache and then cachefiles, attempting to add the pieces in a logical order. Finally, the filesystems are reenabled and then the very last patch changes the documentation.
WHY REWRITE?
============Fscache's operation scheduling API was intended to handle sequencing of cache operations, which were all required (where possible) to run asynchronously in parallel with the operations being done by the network filesystem, whilst allowing the cache to be brought online and offline and to interrupt service for invalidation.
With the advent of the tmpfile capacity in the VFS, however, an opportunity arises to do invalidation much more simply, without having to wait for I/O that's actually in progress: Cachefiles can simply create a tmpfile, cut over the file pointer for the backing object attached to a cookie and abandon the in-progress I/O, dismissing it upon completion.
Future work here would involve using Omar Sandoval's
vfs_link()with AT_LINK_REPLACE[1] to allow an extant file to be displaced by a new hard link from a tmpfile as currently I have to unlink the old file first.These patches can also simplify the object state handling as I/O operations to the cache don't all have to be brought to a stop in order to invalidate a file. To that end, and with an eye on to writing a new backing cache model in the future, I've taken the opportunity to simplify the indexing structure.
I've separated the index cookie concept from the file cookie concept by C type now. The former is now called a "volume cookie" (struct
fscache_volume) and there is a container of file cookies. There are then just the two levels. All the index cookie levels are collapsed into a single volume cookie, and this has a single printable string as a key. For instance, an AFS volume would have a key of something like "afs,example.com,1000555", combining the filesystem name, cell name and volume ID. This is freeform, but must not have '/' chars in it.I've also eliminated all pointers back from fscache into the network filesystem. This required the duplication of a little bit of data in the cookie (cookie key, coherency data and file size), but it's not actually that much. This gets rid of problems with making sure we keep netfs data structures around so that the cache can access them.
These patches mean that most of the code that was in the drivers before is simply gone and those drivers are now almost entirely new code. That being the case, there doesn't seem any particular reason to try and maintain bisectability across it. Further, there has to be a point in the middle where things are cut over as there's a single point everything has to go through (ie.
/dev/cachefiles) and it can't be in use by two drivers at once.ISSUES YET OUTSTANDING
======================There are some issues still outstanding, unaddressed by this patchset, that will need fixing in future patchsets, but that don't stop this series from being usable:
(1) The cachefiles driver needs to stop using the backing filesystem's metadata to store information about what parts of the cache are populated. This is not reliable with modern extent-based filesystems.
Fixing this is deferred to a separate patchset as it involves negotiation with the network filesystem and the VM as to how much data to download to fulfil a read - which brings me on to (2)...
(2) NFS and CIFS do not take account of how the cache would like I/O to be structured to meet its granularity requirements. Previously, the cache used page granularity, which was fine as the network filesystems also dealt in page granularity, and the backing filesystem (ext4, xfs or whatever) did whatever it did out of sight. However, we now have folios to deal with and the cache will now have to store its own metadata to track its contents.
The change I'm looking at making for cachefiles is to store content bitmaps in one or more xattrs and making a bit in the map correspond to something like a 256KiB block. However, the size of an xattr and the fact that they have to be read/updated in one go means that I'm looking at covering 1GiB of data per 512-byte map and storing each map in an xattr. Cachefiles has the potential to grow into a fully fledged filesystem of its very own if I'm not careful.
However, I'm also looking at changing things even more radically and going to a different model of how the cache is arranged and managed - one that's more akin to the way, say, openafs does things - which brings me on to (3)...
(3) The way cachefilesd does culling is very inefficient for large caches and it would be better to move it into the kernel if I can as cachefilesd has to keep asking the kernel if it can cull a file. Changing the way the backend works would allow this to be addressed.
BITS THAT MAY BE CONTROVERSIAL
==============================There are some bits I've added that may be controversial:
(1) I've provided a flag,
S_KERNEL_FILE, that cachefiles uses to check if a files is already being used by some other kernel service (e.g. a duplicate cachefiles cache in the same directory) and reject it if it is. This isn't entirely necessary, but it helps prevent accidental data corruption.I don't want to use
S_SWAPFILEas that has other effects, but quite possiblyswapon()should setS_KERNEL_FILEtoo.Note that it doesn't prevent userspace from interfering, though perhaps it should. (I have made it prevent a marked directory from being rmdir-able).
(2) Cachefiles wants to keep the backing file for a cookie open whilst we might need to write to it from network filesystem writeback. The problem is that the network filesystem unuses its cookie when its file is closed, and so we have nothing pinning the cachefiles file open and it will get closed automatically after a short time to avoid EMFILE/ENFILE problems.
Reopening the cache file, however, is a problem if this is being done due to writeback triggered by
exit(). Some filesystems will oops if we try to open a file in that context because they want to accesscurrent->fsor suchlike.To get around this, I added the following:
(A) An inode flag,
I_PINNING_FSCACHE_WB, to be set on a network filesystem inode to indicate that we have a usage count on the cookie caching that inode.(B) A flag in struct
writeback_control,unpinned_fscache_wb, that is set when__writeback_single_inode()clears the last dirty page fromi_pages- at which point it clearsI_PINNING_FSCACHE_WBand sets this flag.This has to be done here so that clearing
I_PINNING_FSCACHE_WBcan be done atomically with the check ofPAGECACHE_TAG_DIRTYthat clearsI_DIRTY_PAGES.(C) A function,
fscache_set_page_dirty(), which if it is not set, setsI_PINNING_FSCACHE_WBand callsfscache_use_cookie()to pin the cache resources.(D) A function,
fscache_unpin_writeback(), to be called by ->write_inode() to unuse the cookie.(E) A function,
fscache_clear_inode_writeback(), to be called when the inode is evicted, beforeclear_inode()is called. This cleans up any lingeringI_PINNING_FSCACHE_WB.The network filesystem can then use these tools to make sure that
fscache_write_to_cache()can write locally modified data to the cache as well as to the server.For the future, I'm working on write helpers for netfs lib that should allow this facility to be removed by keeping track of the dirty regions separately - but that's incomplete at the moment and is also going to be affected by folios, one way or another, since it deals with pages.
These patches can be found also on:
https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs.git/log/?h=fscache-rewrite
David
Changes
=======
ver #2:
- Fix an unused-var warning due toCONFIG_9P_FSCACHE=n.
- Usegfpflags_allow_blocking()rather than using flag directly.
- Fixed some error logging in a couple of cachefiles functions.
- Fixed an error check in the fscache volume allocation.
- Need to unmark an inode we've moved to the graveyard before unlocking.
- Upgraded to -rc4 to allow for upstream changes to cifs.
- Should only change to inval state if can get access to cache.
- Don't holdn_accesseselevated whilst cache is bound to a cookie, but rather add a flag that prevents the state machine from being queued whenn_accessesreaches 0.
- Remove the unused cookie pointer field from thefscache_acquiretracepoint.
- Added missing transition toLRU_DISCARDINGstate.
- Added two ceph patches from Jeff Layton[2].
- RemoveNFS_INO_FSCACHEas it's no longer used.
- In NFS, need to unuse a cookie on file-release, not inode-clear.
- Filled in the NFS cache I/O routines, borrowing from the previously posted fallback I/O code[3].References
==========These patches have been published for review before, firstly as part of a larger set:
Link https://lore.kernel.org/r/159465766378.1376105.11619976251039287525.stgit@warthog.procyon.org.uk/ open hereThen as a cut-down set:
Link https://lore.kernel.org/r/161118128472.1232039.11746799833066425131.stgit@warthog.procyon.org.uk/ # v1 open hereLink https://lore.kernel.org/r/161161025063.2537118.2009249444682241405.stgit@warthog.procyon.org.uk/ # v2 open hereLink https://lore.kernel.org/r/161340385320.1303470.2392622971006879777.stgit@warthog.procyon.org.uk/ # v3 open hereI split out a set to just restructure the I/O, which got merged back in to this one:
Link https://lore.kernel.org/r/163363935000.1980952.15279841414072653108.stgit@warthog.procyon.org.uk/ open hereLink https://lore.kernel.org/r/163189104510.2509237.10805032055807259087.stgit@warthog.procyon.org.uk/ # v2 open here... and a larger set to do the conversion, also merged back into this one:
Older versions of this one:
Proposals/information about the design have been published here:
And requests for information:
I've posted partial patches to try and help 9p and cifs along:
---
Dave Wysochanski (1):
nfs: Convert to new fscache volume/cookie APIDavid Howells (64):
fscache, cachefiles: Disable configuration
cachefiles: Delete the cachefiles driver pending rewrite
fscache: Remove the contents of the fscache driver, pending rewrite
netfs: Display the netfs inode number in thenetfs_readtracepoint
netfs: Pass a flag to ->prepare_write() to say if there's no alloc'd space
fscache: Introduce new driver
fscache: Implement a hash function
fscache: Implement cache registration
fscache: Implement volume registration
fscache: Implement cookie registration
fscache: Implement cache-level access helpers
fscache: Implement volume-level access helpers
fscache: Implement cookie-level access helpers
fscache: Implement functions add/remove a cache
fscache: Provide and use cache methods to lookup/create/free a volume
fscache: Add a function for a cache backend to note an I/O error
fscache: Implement simple cookie state machine
fscache: Implement cookie user counting and resource pinning
fscache: Implement cookie invalidation
fscache: Provide a means to begin an operation
fscache: Count data storage objects in a cache
fscache: Provide read/write stat counters for the cache
fscache: Provide a function to let the netfs update its coherency data
netfs: Pass more information on how to deal with a hole in the cache
fscache: Implement raw I/O interface
fscache: Implement higher-level write I/O interface
vfs, fscache: Implement pinning of cache usage for writeback
fscache: Provide a function to note the release of a page
fscache: Provide a function to resize a cookie
cachefiles: Introduce rewritten driver
cachefiles: Define structs
cachefiles: Add some error injection support
cachefiles: Add a couple of tracepoints for logging errors
cachefiles: Add cache error reporting macro
cachefiles: Add security derivation
cachefiles: Register a miscdev and parse commands over it
cachefiles: Provide a function to check how much space there is vfs, cachefiles: Mark a backing file in use with an inode flag
cachefiles: Implement a function to get/create a directory in the cache
cachefiles: Implement cache registration and withdrawal
cachefiles: Implement volume support
cachefiles: Add tracepoints for calls to the VFS
cachefiles: Implement object lifecycle funcs
cachefiles: Implement key to filename encoding
cachefiles: Implement metadata/coherency data storage in xattrs
cachefiles: Mark a backing file in use with an inode flag
cachefiles: Implement culling daemon commands
cachefiles: Implement backing file wrangling
cachefiles: Implement begin and end I/O operation
cachefiles: Implement cookie resize for truncate
cachefiles: Implement the I/O routines
cachefiles: Allow cachefiles to actually function
fscache, cachefiles: Display stats of no-space events fscache, cachefiles: Display stat of culling events
afs: Handle len being extending over page end in write_begin/write_end
afs: Fixafs_write_end()to handle len > page size
afs: Convert afs to use the new fscache API
afs: Copy local writes to the cache when writing to the server
afs: Skip truncation on the server of data we haven't written yet 9p: Use fscache indexing rewrite and reenable caching
9p: Copy local writes to the cache when writing to the server
nfs: Implement cache I/O by accessing the cache directly
cifs: Support fscache indexing rewrite (untested)
fscache: Rewrite documentationJeff Layton (2):
ceph: conversion to new fscache API
ceph: add fscache writeback support.../filesystems/caching/backend-api.rst | 847 ++++------ .../filesystems/caching/cachefiles.rst | 6 +- Documentation/filesystems/caching/fscache.rst | 525 ++---- Documentation/filesystems/caching/index.rst | 4 +- .../filesystems/caching/netfs-api.rst | 1083 ++++--------- Documentation/filesystems/caching/object.rst | 313 ---- .../filesystems/caching/operations.rst | 210 --- Documentation/filesystems/netfs_library.rst | 16 +- fs/9p/Kconfig | 2 +- fs/9p/cache.c | 193 +-- fs/9p/cache.h | 25 +- fs/9p/v9fs.c | 17 +- fs/9p/v9fs.h | 13 +- fs/9p/vfs_addr.c | 54 +- fs/9p/vfs_dir.c | 11 + fs/9p/vfs_file.c | 3 +- fs/9p/vfs_inode.c | 24 +- fs/9p/vfs_inode_dotl.c | 3 +- fs/9p/vfs_super.c | 3 + fs/afs/Kconfig | 2 +- fs/afs/Makefile | 3 - fs/afs/cache.c | 68 - fs/afs/cell.c | 12 - fs/afs/file.c | 37 +- fs/afs/inode.c | 101 +- fs/afs/internal.h | 37 +- fs/afs/main.c | 14 - fs/afs/super.c | 1 + fs/afs/volume.c | 29 +- fs/afs/write.c | 100 +- fs/cachefiles/Kconfig | 7 + fs/cachefiles/Makefile | 6 +- fs/cachefiles/bind.c | 278 ---- fs/cachefiles/cache.c | 378 +++++ fs/cachefiles/daemon.c | 180 +-- fs/cachefiles/error_inject.c | 46 + fs/cachefiles/interface.c | 747 ++++----- fs/cachefiles/internal.h | 265 ++-- fs/cachefiles/io.c | 330 ++-- fs/cachefiles/key.c | 201 ++- fs/cachefiles/main.c | 22 +- fs/cachefiles/namei.c | 1221 ++++++-------- fs/cachefiles/rdwr.c | 972 ------------ fs/cachefiles/security.c | 2 +- fs/cachefiles/volume.c | 118 ++ fs/cachefiles/xattr.c | 369 ++--- fs/ceph/Kconfig | 2 +- fs/ceph/addr.c | 101 +- fs/ceph/cache.c | 218 +-- fs/ceph/cache.h | 97 +- fs/ceph/caps.c | 3 +- fs/ceph/file.c | 13 +- fs/ceph/inode.c | 22 +- fs/ceph/super.c | 10 +- fs/ceph/super.h | 3 +- fs/cifs/Kconfig | 2 +- fs/cifs/Makefile | 2 +- fs/cifs/cache.c | 105 -- fs/cifs/cifsfs.c | 11 +- fs/cifs/cifsglob.h | 5 +- fs/cifs/connect.c | 12 - fs/cifs/file.c | 64 +- fs/cifs/fscache.c | 319 +--- fs/cifs/fscache.h | 106 +- fs/cifs/inode.c | 36 +- fs/fs-writeback.c | 8 + fs/fscache/Makefile | 6 +- fs/fscache/cache.c | 618 ++++---- fs/fscache/cookie.c | 1402 +++++++++-------- fs/fscache/fsdef.c | 98 -- fs/fscache/internal.h | 315 +--- fs/fscache/io.c | 376 ++++- fs/fscache/main.c | 136 +- fs/fscache/netfs.c | 74 - fs/fscache/object.c | 1125 ------------- fs/fscache/operation.c | 633 -------- fs/fscache/page.c | 1242 --------------- fs/fscache/proc.c | 45 +- fs/fscache/stats.c | 293 +--- fs/fscache/volume.c | 508 ++++++ fs/namei.c | 3 +- fs/netfs/read_helper.c | 10 +- fs/nfs/Kconfig | 2 +- fs/nfs/Makefile | 2 +- fs/nfs/client.c | 4 - fs/nfs/direct.c | 2 + fs/nfs/file.c | 13 +- fs/nfs/fscache-index.c | 140 -- fs/nfs/fscache.c | 490 ++---- fs/nfs/fscache.h | 182 +-- fs/nfs/inode.c | 11 +- fs/nfs/nfstrace.h | 1 - fs/nfs/read.c | 25 +- fs/nfs/super.c | 28 +- fs/nfs/write.c | 8 +- include/linux/fs.h | 4 + include/linux/fscache-cache.h | 614 ++------ include/linux/fscache.h | 1015 +++++------- include/linux/netfs.h | 15 +- include/linux/nfs_fs.h | 1 - include/linux/nfs_fs_sb.h | 9 +- include/linux/writeback.h | 1 + include/trace/events/cachefiles.h | 487 ++++-- include/trace/events/fscache.h | 626 ++++---- include/trace/events/netfs.h | 5 +- 105 files changed, 7121 insertions(+), 13485 deletions(-) delete mode 100644 Documentation/filesystems/caching/object.rst delete mode 100644 Documentation/filesystems/caching/operations.rst delete mode 100644 fs/afs/cache.c delete mode 100644 fs/cachefiles/bind.c create mode 100644 fs/cachefiles/cache.c create mode 100644 fs/cachefiles/error_inject.c delete mode 100644 fs/cachefiles/rdwr.c create mode 100644 fs/cachefiles/volume.c delete mode 100644 fs/cifs/cache.c delete mode 100644 fs/fscache/fsdef.c
Quoted 9 lines
Quoted 8 lines
delete mode 100644 fs/fscache/netfs.c delete mode 100644 fs/fscache/object.c delete mode 100644 fs/fscache/operation.c delete mode 100644 fs/fscache/page.c create mode 100644 fs/fscache/volume.c delete mode 100644 fs/nfs/fscache-index.c
(Re-sending this to cut-down list of addresses since previous attempt did not reach the lists)
Please see the UAF text below. I'm getting this firing periodically with NFSv3 xfstests, but it's hard to reproduce and kasan output varies.
Quoted 140 lines
Testing this with NFS and fscache enabled.
- fscache unit tests: PASS
- xfstests NFSv4.2 (rhel8 server): PASS
- xfstests NFSv4.1 (netapp server): PASS
- xfstests NFSv4.0 (netapp server): PASS
- xfstests NFSv3 (rhel8 server): FAIL (see below use after free w/kasan)NOTE: I had one patch that converted nfs fscache dfprintk's dfprintks to trace events on top of your series, but tracepoints were not enabled, and I don't think my patch was a contributor to this kasan use-after-free. Unfortunately after I rebuilt it, I did not reproduce the problem so far. I wonder if there is a race with
nfs_fscache_open_file(),fscache_use_cookie()and thenfscache_invalidate(), but I've not read through this enough to map out a possible theory. Maybe you can spot it faster than me.[ 405.242590] run fstests generic/011 at 2021-12-10 11:44:26^M [ 432.920087] ==================================================================^M [ 432.921382] BUG: KASAN: use-after-free in fscache_unhash_cookie+0x9e/0x160 [fscache]^M [ 432.922617] Write of size 8 at addr ffff88812c185200 by task kworker/u16:179/8137^M [ 432.923795] ^M [ 432.924059] CPU: 0 PID: 8137 Comm: kworker/u16:179 Kdump: loaded Not tainted 5.16.0-rc4-fscache-rewrite-trace-kasan+ #13^M [ 432.925737] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.14.0-4.fc34 04/01/2014^M [ 432.927057] Workqueue: fscache fscache_cookie_worker [fscache]^M [ 432.928035] Call Trace:^M [ 432.928467] <TASK>^M [ 432.928844] dump_stack_lvl+0x48/0x5e^M [ 432.929447] print_address_description.constprop.0+0x1f/0x140^M [ 432.930363] ? fscache_unhash_cookie+0x9e/0x160 [fscache]^M [ 432.931240] kasan_report.cold+0x7f/0x11b^M [ 432.931891] ? fscache_unhash_cookie+0x9e/0x160 [fscache]^M [ 432.932773] fscache_unhash_cookie+0x9e/0x160 [fscache]^M [ 432.933626] fscache_cookie_worker+0x1f0/0xad0 [fscache]^M [ 432.934477] ? _raw_spin_unlock_bh+0x20/0x20^M [ 432.935180] ? __list_add_valid+0x2f/0x60^M [ 432.935820] process_one_work+0x3d2/0x710^M [ 432.936702] worker_thread+0x2d2/0x6e0^M [ 432.937475] ? process_one_work+0x710/0x710^M [ 432.938177] kthread+0x223/0x260^M [ 432.938711] ? set_kthread_struct+0x80/0x80^M [ 432.939395] ret_from_fork+0x22/0x30^M [ 432.940010] </TASK>^M [ 432.940388] ^M [ 432.940672] Allocated by task 9139:^M [ 432.941253] kasan_save_stack+0x1e/0x50^M [ 432.941888] __kasan_slab_alloc+0x66/0x80^M [ 432.942553] kmem_cache_alloc+0x147/0x2c0^M [ 432.943233] __fscache_acquire_cookie+0xa1/0x9b0 [fscache]^M [ 432.944125] nfs_fscache_init_inode+0x2dc/0x340 [nfs]^M [ 432.945038] nfs_fhget+0x757/0xcd0 [nfs]^M [ 432.945745] nfs_add_or_obtain+0x163/0x190 [nfs]^M [ 432.946563] nfs3_proc_create+0x1e0/0x4f0 [nfsv3]^M [ 432.947324] nfs_create+0x106/0x270 [nfs]^M [ 432.948054] path_openat+0x14ec/0x1810^M [ 432.948679] do_filp_open+0x131/0x230^M [ 432.949281] do_sys_openat2+0xe4/0x240^M [ 432.949903] __x64_sys_creat+0x99/0xb0^M [ 432.949903] __x64_sys_creat+0x99/0xb0^M [ 432.950520] do_syscall_64+0x3b/0x90^M [ 432.951113] entry_SYSCALL_64_after_hwframe+0x44/0xae^M [ 432.951932] ^M [ 432.952207] Freed by task 8191:^M [ 432.952727] kasan_save_stack+0x1e/0x50^M [ 432.953369] kasan_set_track+0x21/0x30^M [ 432.953969] kasan_set_free_info+0x20/0x30^M [ 432.954626] __kasan_slab_free+0xec/0x120^M [ 432.955288] slab_free_freelist_hook+0x66/0x130^M [ 432.956002] kmem_cache_free+0x108/0x400^M [ 432.956640] fscache_put_cookie+0x10f/0x150 [fscache]^M [ 432.957454] process_one_work+0x3d2/0x710^M [ 432.958111] worker_thread+0x2d2/0x6e0^M [ 432.958761] kthread+0x223/0x260^M [ 432.959298] ret_from_fork+0x22/0x30^M [ 432.959884] ^M [ 432.960151] Last potentially related work creation:^M [ 432.960914] kasan_save_stack+0x1e/0x50^M [ 432.961531] __kasan_record_aux_stack+0xae/0xc0^M [ 432.962255] insert_work+0x34/0x190^M [ 432.962825] __queue_work+0x336/0x680^M [ 432.963412] queue_work_on+0x60/0x70^M [ 432.963983] __fscache_withdraw_cookie+0xab/0x160 [fscache]^M [ 432.964860] fscache_cookie_lru_worker+0x227/0x2f0 [fscache]^M [ 432.965746] process_one_work+0x3d2/0x710^M [ 432.966394] worker_thread+0x2d2/0x6e0^M [ 432.966982] kthread+0x223/0x260^M [ 432.967515] ret_from_fork+0x22/0x30^M [ 432.968088] ^M [ 432.968354] Second to last potentially related work creation:^M [ 432.969236] kasan_save_stack+0x1e/0x50^M [ 432.969843] __kasan_record_aux_stack+0xae/0xc0^M [ 432.970554] insert_work+0x34/0x190^M [ 432.971109] __queue_work+0x336/0x680^M [ 432.971688] queue_work_on+0x60/0x70^M [ 432.972252] __fscache_use_cookie+0x25b/0x370 [fscache]^M [ 432.973083] nfs_fscache_open_file+0xb0/0x230 [nfs]^M [ 432.973918] nfs_open+0x7a/0xc0 [nfs]^M [ 432.974592] do_dentry_open+0x28c/0x690^M [ 432.975203] path_openat+0x1139/0x1810^M [ 432.975796] do_filp_open+0x131/0x230^M [ 432.976374] do_sys_openat2+0xe4/0x240^M [ 432.976978] __x64_sys_creat+0x99/0xb0^M [ 432.977573] do_syscall_64+0x3b/0x90^M [ 432.978147] entry_SYSCALL_64_after_hwframe+0x44/0xae^M [ 432.978927] ^M [ 432.979191] The buggy address belongs to the object at ffff88812c1851d0^M [ 432.979191] which belongs to the cache fscache_cookie_jar of size 176^M [ 432.981138] The buggy address is located 48 bytes inside of^M [ 432.981138] 176-byte region [ffff88812c1851d0, ffff88812c185280)^M [ 432.982859] The buggy address belongs to the page:^M [ 432.983604] page:00000000200db521 refcount:1 mapcount:0 mapping:0000000000000000 index:0xffff88812c1852c0 pfn:0x12c184^M [ 432.985230] head:00000000200db521 order:1 compound_mapcount:0^M [ 432.986102] flags: 0x17ffffc0010200(slab|head|node=0|zone=2|lastcpupid=0x1fffff)^M [ 432.987245] raw: 0017ffffc0010200 ffffea00042d6f00 dead000000000004 ffff888110b8d180^M [ 432.988413] raw: ffff88812c1852c0 0000000080220020 00000001ffffffff 0000000000000000^M [ 432.989589] page dumped because: kasan: bad access detected^M [ 432.990470] ^M [ 432.990751] Memory state around the buggy address:^M [ 432.991487] ffff88812c185100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb^M [ 432.992586] ffff88812c185180: fb fb fc fc fc fc fc fc fc fc fa fb fb fb fb fb^M [ 432.993696] >ffff88812c185200: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb^M [ 432.994803] ^^M [ 432.995330] ffff88812c185280: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb^M [ 432.996439] ffff88812c185300: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fc fc^M [ 432.997536] ==================================================================^M [ 432.998622] Disabling lock debugging due to kernel taint^M [ 452.411206] run fstests generic/012 at 2021-12-10 11:45:14^M
David Howells
maintainerPATCH 1/1 fscache: Need to go round again after processing LRU_DISCARDING state
Quoted 2 lines · David Wysochanski
Quoted 2 lines
[ 432.921382] BUG: KASAN: use-after-free in
fscache_unhash_cookie+0x9e/0x160[fscache]^M
I think the patch below is the way to fix this.
David
---
fscache: Need to go round again after processing LRU_DISCARDING state
There's a race between the LRU discard and relinquishment actions. In the state machine, fscache_cookie_state_machine(), the ACTIVE state transits to the LRU_DISCARD state in preference to transiting to the RELINQUISHING or WITHDRAWING states.
This should be fine, but the LRU_DISCARDING state just breaks out the bottom of the function without going round again after transiting to the QUIESCENT state.
However, if both LRU discard and relinquishment happen *before* the SM runs, one of the queue events will get discarded, along with the ref that would be associated with it. The last ref is then discarded and the cookie is removed without completing the relinquishment process - leaving the cookie hashed.
The fix is to make sure that the SM always goes back around after changing the state.
---
fs/fscache/cookie.c +1 -1
@@ -755,7 +755,7 @@ static void fscache_cookie_state_machine(struct fscache_cookie *cookie)set_bit (FSCACHE_COOKIE_NO_DATA_TO_READ , &cookie->flags);__fscache_set_cookie_state (cookie,FSCACHE_COOKIE_STATE_QUIESCENT ); wake = true; -break ; + goto again_locked;case FSCACHE_COOKIE_STATE_DROPPED :break ;
David Howells
maintainerRe: [PATCH] fscache: Need to go round again after processing LRU_DISCARDING state
Quoted 5 lines · David Howells
However, if both LRU discard and relinquishment happen *before* the SM runs, one of the queue events will get discarded, along with the ref that would be associated with it. The last ref is then discarded and the cookie is removed without completing the relinquishment process - leaving the cookie hashed.
This can be seen in a trace, e.g.:
kworker/u16:97-5939 [000] ..... 639.403740: fscache_cookie: c=000071a9 - lrudo r=3
kworker/u16:97-5939 [000] ..... 639.403741: fscache_cookie: c=000071a9 GQ endac r=4
kworker/u16:97-5939 [000] ..... 639.403745: fscache_cookie: c=000071a9 PUT lru r=3
dirstress-7027 [002] ..... 639.427220: fscache_relinquish: c=000071a9 V=00000001 r=3 U=0 f=bd rt=0
dirstress-7027 [002] ..... 639.427222: fscache_cookie: c=000071a9 GQ endac r=4
dirstress-7027 [002] ..... 639.427223: fscache_cookie: c=000071a9 PQ overq r=3where the "overq" line marks the discarded event and ref.
David
David Howells
maintainerRe: [PATCH] fscache: Need to go round again after processing LRU_DISCARDING state
In reply to David HowellsI forgot to commit part of the patch. Attached is the patch in full.
David
---
commit a3d01f1a21bcf2c39aa6db49edbc08540378a593
Author: David Howells <[email protected]> Date: Mon Dec 13 16:26:44 2021 +0000
afs: Fix mmap
Fix afs_add_open_map() to check that the vnode isn't already on the list when it adds it. It's possible that afs_drop_open_mmap() decremented the cb_nr_mmap counter, but hadn't yet got into the locked section to remove it.
Also vnode->cb_mmap_link should be initialised, so fix that too.
fs/afs/file.c +3 -2
@@ -539,8 +539,9 @@ static void afs_add_open_mmap(struct afs_vnode *vnode)if (atomic_inc_return (&vnode->cb_nr_mmap) ==1 ) {down_write (&vnode->volume->cell->fs_open_mmaps_lock); -list_add_tail (&vnode->cb_mmap_link, - &vnode->volume->cell->fs_open_mmaps); +if (list_empty (&vnode->cb_mmap_link)) +list_add_tail (&vnode->cb_mmap_link, + &vnode->volume->cell->fs_open_mmaps);up_write (&vnode->volume->cell->fs_open_mmaps_lock); }
fs/afs/super.c +1 -0
@@ -668,6 +668,7 @@ static void afs_i_init_once(void *_vnode)INIT_LIST_HEAD (&vnode->pending_locks);INIT_LIST_HEAD (&vnode->granted_locks);INIT_DELAYED_WORK (&vnode->lock_work, afs_lock_work); +INIT_LIST_HEAD (&vnode->cb_mmap_link);seqlock_init (&vnode->cb_lock); }
David Howells
maintainerRe: [PATCH] fscache: Need to go round again after processing LRU_DISCARDING state
Quoted 1 line · David Howells
I forgot to commit part of the patch. Attached is the patch in full.
Sigh. I replied to the wrong message.
David
Re: [PATCH] fscache: Need to go round again after processing LRU_DISCARDING state
In reply to David HowellsQuoted 39 lines · David Howells
Quoted 2 lines · David Wysochanski
Quoted 2 lines
[ 432.921382] BUG: KASAN: use-after-free in
fscache_unhash_cookie+0x9e/0x160[fscache]^MI think the patch below is the way to fix this.
David
---
fscache: Need to go round again after processingLRU_DISCARDINGstateThere's a race between the LRU discard and relinquishment actions. In the state machine,
fscache_cookie_state_machine(), the ACTIVE state transits to theLRU_DISCARDstate in preference to transiting to the RELINQUISHING or WITHDRAWING states.This should be fine, but the
LRU_DISCARDINGstate just breaks out the bottom of the function without going round again after transiting to the QUIESCENT state.However, if both LRU discard and relinquishment happen *before* the SM runs, one of the queue events will get discarded, along with the ref that would be associated with it. The last ref is then discarded and the cookie is removed without completing the relinquishment process - leaving the cookie hashed.
The fix is to make sure that the SM always goes back around after changing the state.
Signed-off-by David Howells <[email protected]>---
fs/fscache/cookie.c +0
-0
fs/fscache/cookie.c static void fscache_cookie_state_machine(struct fscache_cookie *cookie) · line 761
Quoted 8 lines
fs/fscache/cookie.c +1
-1@@ -755,7 +755,7 @@ static void fscache_cookie_state_machine(struct fscache_cookie *cookie)set_bit (FSCACHE_COOKIE_NO_DATA_TO_READ , &cookie->flags);__fscache_set_cookie_state (cookie,FSCACHE_COOKIE_STATE_QUIESCENT ); wake = true; -break ; + goto again_locked;case FSCACHE_COOKIE_STATE_DROPPED :break ;
Agree and verified with xfstests generic full runs twice with NFSv3. Prior to this patch with NFSv3 xfstest I'd regularly see the crash:
BUG: KASAN: use-after-free in __fscache_acquire_cookie+0x437
https://marc.info/?l=v9fs-developer&m=163916153103008&w=2
https://marc.info/?l=linux-nfs&m=163917893813589&w=2