![David Howells](/assets/img/avatar_default.png)
Simplify the way fscache cache objects retain their cookie. The way I implemented the cookie storage handling made synchronisation a pain (ie. the object state machine can't rely on the cookie actually still being there). Instead of the the object being detached from the cookie and the cookie being freed in __fscache_relinquish_cookie(), we defer both operations: (*) The detachment of the object from the list in the cookie now takes place in fscache_drop_object() and is thus governed by the object state machine (fscache_detach_from_cookie() has been removed). (*) The release of the cookie is now in fscache_object_destroy() - which is called by the cache backend just before it frees the object. This means that the fscache_cookie struct is now available to the cache all the way through from ->alloc_object() to ->drop_object() and ->put_object() - meaning that it's no longer necessary to take object->lock to guarantee access. However, __fscache_relinquish_cookie() doesn't wait for the object to go all the way through to destruction before letting the netfs proceed. That would massively slow down the netfs. Since __fscache_relinquish_cookie() leaves the cookie around, in must therefore break all attachments to the netfs - which includes ->def, ->netfs_data and any outstanding page read/writes. To handle this, struct fscache_cookie now has an n_active counter: (1) This starts off initialised to 1. (2) Any time the cache needs to get at the netfs data, it calls fscache_use_cookie() to increment it - if it is not zero. If it was zero, then access is not permitted. (3) When the cache has finished with the data, it calls fscache_unuse_cookie() to decrement it. This does a wake-up on it if it reaches 0. (4) __fscache_relinquish_cookie() decrements n_active and then waits for it to reach 0. The initialisation to 1 in step (1) ensures that we only get wake ups when we're trying to get rid of the cookie. This leaves __fscache_relinquish_cookie() a lot simpler. *** This fixes a problem in the current code whereby if fscache_invalidate() is followed sufficiently quickly by fscache_relinquish_cookie() then it is possible for __fscache_relinquish_cookie() to have detached the cookie from the object and cleared the pointer before a thread is dispatched to process the invalidation state in the object state machine. Since the pending write clearance was deferred to the invalidation state to make it asynchronous, we need to either wait in relinquishment for the stores tree to be cleared in the invalidation state or we need to handle the clearance in relinquishment. Further, if the relinquishment code does clear the tree, then the invalidation state need to make the clearance contingent on still having the cookie to hand (since that's where the tree is rooted) and we have to prevent the cookie from disappearing for the duration. This can lead to an oops like the following: BUG: unable to handle kernel NULL pointer dereference at 000000000000000c ... RIP: 0010:[<ffffffff8151023e>] _spin_lock+0xe/0x30 ... CR2: 000000000000000c ... ... Process kslowd002 (...) .... Call Trace: [<ffffffffa01c3278>] fscache_invalidate_writes+0x38/0xd0 [fscache] [<ffffffff810096f0>] ? __switch_to+0xd0/0x320 [<ffffffff8105e759>] ? find_busiest_queue+0x69/0x150 [<ffffffff8110ddd4>] ? slow_work_enqueue+0x104/0x180 [<ffffffffa01c1303>] fscache_object_slow_work_execute+0x5e3/0x9d0 [fscache] [<ffffffff81096b67>] ? bit_waitqueue+0x17/0xd0 [<ffffffff8110e233>] slow_work_execute+0x233/0x310 [<ffffffff8110e515>] slow_work_thread+0x205/0x360 [<ffffffff81096ca0>] ? autoremove_wake_function+0x0/0x40 [<ffffffff8110e310>] ? slow_work_thread+0x0/0x360 [<ffffffff81096936>] kthread+0x96/0xa0 [<ffffffff8100c0ca>] child_rip+0xa/0x20 [<ffffffff810968a0>] ? kthread+0x0/0xa0 [<ffffffff8100c0c0>] ? child_rip+0x0/0x20 The parameter to fscache_invalidate_writes() was object->cookie which is NULL. Signed-off-by: David Howells <dhowells@redhat.com> Tested-By: Milosz Tanski <milosz@adfin.com> Acked-by: Jeff Layton <jlayton@redhat.com>
291 lines
6.3 KiB
C
291 lines
6.3 KiB
C
/* CacheFiles extended attribute management
|
|
*
|
|
* Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public Licence
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the Licence, or (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/file.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/fsnotify.h>
|
|
#include <linux/quotaops.h>
|
|
#include <linux/xattr.h>
|
|
#include <linux/slab.h>
|
|
#include "internal.h"
|
|
|
|
static const char cachefiles_xattr_cache[] =
|
|
XATTR_USER_PREFIX "CacheFiles.cache";
|
|
|
|
/*
|
|
* check the type label on an object
|
|
* - done using xattrs
|
|
*/
|
|
int cachefiles_check_object_type(struct cachefiles_object *object)
|
|
{
|
|
struct dentry *dentry = object->dentry;
|
|
char type[3], xtype[3];
|
|
int ret;
|
|
|
|
ASSERT(dentry);
|
|
ASSERT(dentry->d_inode);
|
|
|
|
if (!object->fscache.cookie)
|
|
strcpy(type, "C3");
|
|
else
|
|
snprintf(type, 3, "%02x", object->fscache.cookie->def->type);
|
|
|
|
_enter("%p{%s}", object, type);
|
|
|
|
/* attempt to install a type label directly */
|
|
ret = vfs_setxattr(dentry, cachefiles_xattr_cache, type, 2,
|
|
XATTR_CREATE);
|
|
if (ret == 0) {
|
|
_debug("SET"); /* we succeeded */
|
|
goto error;
|
|
}
|
|
|
|
if (ret != -EEXIST) {
|
|
kerror("Can't set xattr on %*.*s [%lu] (err %d)",
|
|
dentry->d_name.len, dentry->d_name.len,
|
|
dentry->d_name.name, dentry->d_inode->i_ino,
|
|
-ret);
|
|
goto error;
|
|
}
|
|
|
|
/* read the current type label */
|
|
ret = vfs_getxattr(dentry, cachefiles_xattr_cache, xtype, 3);
|
|
if (ret < 0) {
|
|
if (ret == -ERANGE)
|
|
goto bad_type_length;
|
|
|
|
kerror("Can't read xattr on %*.*s [%lu] (err %d)",
|
|
dentry->d_name.len, dentry->d_name.len,
|
|
dentry->d_name.name, dentry->d_inode->i_ino,
|
|
-ret);
|
|
goto error;
|
|
}
|
|
|
|
/* check the type is what we're expecting */
|
|
if (ret != 2)
|
|
goto bad_type_length;
|
|
|
|
if (xtype[0] != type[0] || xtype[1] != type[1])
|
|
goto bad_type;
|
|
|
|
ret = 0;
|
|
|
|
error:
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
|
|
bad_type_length:
|
|
kerror("Cache object %lu type xattr length incorrect",
|
|
dentry->d_inode->i_ino);
|
|
ret = -EIO;
|
|
goto error;
|
|
|
|
bad_type:
|
|
xtype[2] = 0;
|
|
kerror("Cache object %*.*s [%lu] type %s not %s",
|
|
dentry->d_name.len, dentry->d_name.len,
|
|
dentry->d_name.name, dentry->d_inode->i_ino,
|
|
xtype, type);
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* set the state xattr on a cache file
|
|
*/
|
|
int cachefiles_set_object_xattr(struct cachefiles_object *object,
|
|
struct cachefiles_xattr *auxdata)
|
|
{
|
|
struct dentry *dentry = object->dentry;
|
|
int ret;
|
|
|
|
ASSERT(dentry);
|
|
|
|
_enter("%p,#%d", object, auxdata->len);
|
|
|
|
/* attempt to install the cache metadata directly */
|
|
_debug("SET #%u", auxdata->len);
|
|
|
|
ret = vfs_setxattr(dentry, cachefiles_xattr_cache,
|
|
&auxdata->type, auxdata->len,
|
|
XATTR_CREATE);
|
|
if (ret < 0 && ret != -ENOMEM)
|
|
cachefiles_io_error_obj(
|
|
object,
|
|
"Failed to set xattr with error %d", ret);
|
|
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* update the state xattr on a cache file
|
|
*/
|
|
int cachefiles_update_object_xattr(struct cachefiles_object *object,
|
|
struct cachefiles_xattr *auxdata)
|
|
{
|
|
struct dentry *dentry = object->dentry;
|
|
int ret;
|
|
|
|
ASSERT(dentry);
|
|
|
|
_enter("%p,#%d", object, auxdata->len);
|
|
|
|
/* attempt to install the cache metadata directly */
|
|
_debug("SET #%u", auxdata->len);
|
|
|
|
ret = vfs_setxattr(dentry, cachefiles_xattr_cache,
|
|
&auxdata->type, auxdata->len,
|
|
XATTR_REPLACE);
|
|
if (ret < 0 && ret != -ENOMEM)
|
|
cachefiles_io_error_obj(
|
|
object,
|
|
"Failed to update xattr with error %d", ret);
|
|
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* check the state xattr on a cache file
|
|
* - return -ESTALE if the object should be deleted
|
|
*/
|
|
int cachefiles_check_object_xattr(struct cachefiles_object *object,
|
|
struct cachefiles_xattr *auxdata)
|
|
{
|
|
struct cachefiles_xattr *auxbuf;
|
|
struct dentry *dentry = object->dentry;
|
|
int ret;
|
|
|
|
_enter("%p,#%d", object, auxdata->len);
|
|
|
|
ASSERT(dentry);
|
|
ASSERT(dentry->d_inode);
|
|
|
|
auxbuf = kmalloc(sizeof(struct cachefiles_xattr) + 512, cachefiles_gfp);
|
|
if (!auxbuf) {
|
|
_leave(" = -ENOMEM");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* read the current type label */
|
|
ret = vfs_getxattr(dentry, cachefiles_xattr_cache,
|
|
&auxbuf->type, 512 + 1);
|
|
if (ret < 0) {
|
|
if (ret == -ENODATA)
|
|
goto stale; /* no attribute - power went off
|
|
* mid-cull? */
|
|
|
|
if (ret == -ERANGE)
|
|
goto bad_type_length;
|
|
|
|
cachefiles_io_error_obj(object,
|
|
"Can't read xattr on %lu (err %d)",
|
|
dentry->d_inode->i_ino, -ret);
|
|
goto error;
|
|
}
|
|
|
|
/* check the on-disk object */
|
|
if (ret < 1)
|
|
goto bad_type_length;
|
|
|
|
if (auxbuf->type != auxdata->type)
|
|
goto stale;
|
|
|
|
auxbuf->len = ret;
|
|
|
|
/* consult the netfs */
|
|
if (object->fscache.cookie->def->check_aux) {
|
|
enum fscache_checkaux result;
|
|
unsigned int dlen;
|
|
|
|
dlen = auxbuf->len - 1;
|
|
|
|
_debug("checkaux %s #%u",
|
|
object->fscache.cookie->def->name, dlen);
|
|
|
|
result = fscache_check_aux(&object->fscache,
|
|
&auxbuf->data, dlen);
|
|
|
|
switch (result) {
|
|
/* entry okay as is */
|
|
case FSCACHE_CHECKAUX_OKAY:
|
|
goto okay;
|
|
|
|
/* entry requires update */
|
|
case FSCACHE_CHECKAUX_NEEDS_UPDATE:
|
|
break;
|
|
|
|
/* entry requires deletion */
|
|
case FSCACHE_CHECKAUX_OBSOLETE:
|
|
goto stale;
|
|
|
|
default:
|
|
BUG();
|
|
}
|
|
|
|
/* update the current label */
|
|
ret = vfs_setxattr(dentry, cachefiles_xattr_cache,
|
|
&auxdata->type, auxdata->len,
|
|
XATTR_REPLACE);
|
|
if (ret < 0) {
|
|
cachefiles_io_error_obj(object,
|
|
"Can't update xattr on %lu"
|
|
" (error %d)",
|
|
dentry->d_inode->i_ino, -ret);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
okay:
|
|
ret = 0;
|
|
|
|
error:
|
|
kfree(auxbuf);
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
|
|
bad_type_length:
|
|
kerror("Cache object %lu xattr length incorrect",
|
|
dentry->d_inode->i_ino);
|
|
ret = -EIO;
|
|
goto error;
|
|
|
|
stale:
|
|
ret = -ESTALE;
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* remove the object's xattr to mark it stale
|
|
*/
|
|
int cachefiles_remove_object_xattr(struct cachefiles_cache *cache,
|
|
struct dentry *dentry)
|
|
{
|
|
int ret;
|
|
|
|
ret = vfs_removexattr(dentry, cachefiles_xattr_cache);
|
|
if (ret < 0) {
|
|
if (ret == -ENOENT || ret == -ENODATA)
|
|
ret = 0;
|
|
else if (ret != -ENOMEM)
|
|
cachefiles_io_error(cache,
|
|
"Can't remove xattr from %lu"
|
|
" (error %d)",
|
|
dentry->d_inode->i_ino, -ret);
|
|
}
|
|
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
}
|