Skip to content

Commit

Permalink
cachefiles: Implement backing file wrangling
Browse files Browse the repository at this point in the history
Implement the wrangling of backing files, including the following pieces:

 (1) Lookup and creation of a file on disk, using a tmpfile if the file
     isn't yet present.  The file is then opened, sized for DIO and the
     file handle is attached to the cachefiles_object struct.  The inode is
     marked to indicate that it's in use by a kernel service.

 (2) Invalidation of an object, creating a tmpfile and switching the file
     pointer in the cachefiles object.

 (3) Committing a file to disk, including setting the coherency xattr on it
     and, if necessary, creating a hard link to it.

     Note that this would be a good place to use Omar Sandoval's vfs_link()
     with AT_LINK_REPLACE[1] as I may have to unlink an old file before I
     can link a tmpfile into place.

 (4) Withdrawal of open objects when a cache is being withdrawn or a cookie
     is relinquished.  This involves committing or discarding the file.

Changes
=======
ver #2:
 - Fix logging of wrong error[1].

Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
cc: linux-cachefs@redhat.com
Link: https://lore.kernel.org/r/20211203094950.GA2480@kili/ [1]
Link: https://lore.kernel.org/r/163819644097.215744.4505389616742411239.stgit@warthog.procyon.org.uk/ # v1
Link: https://lore.kernel.org/r/163906949512.143852.14222856795032602080.stgit@warthog.procyon.org.uk/ # v2
Link: https://lore.kernel.org/r/163967158526.1823006.17482695321424642675.stgit@warthog.procyon.org.uk/ # v3
Link: https://lore.kernel.org/r/164021557060.640689.16373541458119269871.stgit@warthog.procyon.org.uk/ # v4
  • Loading branch information
dhowells committed Jan 7, 2022
1 parent 07a90e9 commit 1f08c92
Show file tree
Hide file tree
Showing 5 changed files with 619 additions and 1 deletion.
32 changes: 31 additions & 1 deletion fs/cachefiles/cache.c
Expand Up @@ -262,6 +262,36 @@ int cachefiles_has_space(struct cachefiles_cache *cache,
return ret;
}

/*
* Mark all the objects as being out of service and queue them all for cleanup.
*/
static void cachefiles_withdraw_objects(struct cachefiles_cache *cache)
{
struct cachefiles_object *object;
unsigned int count = 0;

_enter("");

spin_lock(&cache->object_list_lock);

while (!list_empty(&cache->object_list)) {
object = list_first_entry(&cache->object_list,
struct cachefiles_object, cache_link);
cachefiles_see_object(object, cachefiles_obj_see_withdrawal);
list_del_init(&object->cache_link);
fscache_withdraw_cookie(object->cookie);
count++;
if ((count & 63) == 0) {
spin_unlock(&cache->object_list_lock);
cond_resched();
spin_lock(&cache->object_list_lock);
}
}

spin_unlock(&cache->object_list_lock);
_leave(" [%u objs]", count);
}

/*
* Withdraw volumes.
*/
Expand Down Expand Up @@ -326,7 +356,7 @@ void cachefiles_withdraw_cache(struct cachefiles_cache *cache)
/* we now have to destroy all the active objects pertaining to this
* cache - which we do by passing them off to thread pool to be
* disposed of */
// PLACEHOLDER: Withdraw objects
cachefiles_withdraw_objects(cache);
fscache_wait_for_objects(fscache);

cachefiles_withdraw_volumes(cache);
Expand Down
1 change: 1 addition & 0 deletions fs/cachefiles/daemon.c
Expand Up @@ -106,6 +106,7 @@ static int cachefiles_daemon_open(struct inode *inode, struct file *file)
mutex_init(&cache->daemon_mutex);
init_waitqueue_head(&cache->daemon_pollwq);
INIT_LIST_HEAD(&cache->volumes);
INIT_LIST_HEAD(&cache->object_list);
spin_lock_init(&cache->object_list_lock);

/* set default caching limits
Expand Down
260 changes: 260 additions & 0 deletions fs/cachefiles/interface.c
Expand Up @@ -99,8 +99,268 @@ void cachefiles_put_object(struct cachefiles_object *object,
_leave("");
}

/*
* Adjust the size of a cache file if necessary to match the DIO size. We keep
* the EOF marker a multiple of DIO blocks so that we don't fall back to doing
* non-DIO for a partial block straddling the EOF, but we also have to be
* careful of someone expanding the file and accidentally accreting the
* padding.
*/
static int cachefiles_adjust_size(struct cachefiles_object *object)
{
struct iattr newattrs;
struct file *file = object->file;
uint64_t ni_size;
loff_t oi_size;
int ret;

ni_size = object->cookie->object_size;
ni_size = round_up(ni_size, CACHEFILES_DIO_BLOCK_SIZE);

_enter("{OBJ%x},[%llu]",
object->debug_id, (unsigned long long) ni_size);

if (!file)
return -ENOBUFS;

oi_size = i_size_read(file_inode(file));
if (oi_size == ni_size)
return 0;

inode_lock(file_inode(file));

/* if there's an extension to a partial page at the end of the backing
* file, we need to discard the partial page so that we pick up new
* data after it */
if (oi_size & ~PAGE_MASK && ni_size > oi_size) {
_debug("discard tail %llx", oi_size);
newattrs.ia_valid = ATTR_SIZE;
newattrs.ia_size = oi_size & PAGE_MASK;
ret = cachefiles_inject_remove_error();
if (ret == 0)
ret = notify_change(&init_user_ns, file->f_path.dentry,
&newattrs, NULL);
if (ret < 0)
goto truncate_failed;
}

newattrs.ia_valid = ATTR_SIZE;
newattrs.ia_size = ni_size;
ret = cachefiles_inject_write_error();
if (ret == 0)
ret = notify_change(&init_user_ns, file->f_path.dentry,
&newattrs, NULL);

truncate_failed:
inode_unlock(file_inode(file));

if (ret < 0)
trace_cachefiles_io_error(NULL, file_inode(file), ret,
cachefiles_trace_notify_change_error);
if (ret == -EIO) {
cachefiles_io_error_obj(object, "Size set failed");
ret = -ENOBUFS;
}

_leave(" = %d", ret);
return ret;
}

/*
* Attempt to look up the nominated node in this cache
*/
static bool cachefiles_lookup_cookie(struct fscache_cookie *cookie)
{
struct cachefiles_object *object;
struct cachefiles_cache *cache = cookie->volume->cache->cache_priv;
const struct cred *saved_cred;
bool success;

object = cachefiles_alloc_object(cookie);
if (!object)
goto fail;

_enter("{OBJ%x}", object->debug_id);

if (!cachefiles_cook_key(object))
goto fail_put;

cookie->cache_priv = object;

cachefiles_begin_secure(cache, &saved_cred);

success = cachefiles_look_up_object(object);
if (!success)
goto fail_withdraw;

cachefiles_see_object(object, cachefiles_obj_see_lookup_cookie);

spin_lock(&cache->object_list_lock);
list_add(&object->cache_link, &cache->object_list);
spin_unlock(&cache->object_list_lock);
cachefiles_adjust_size(object);

cachefiles_end_secure(cache, saved_cred);
_leave(" = t");
return true;

fail_withdraw:
cachefiles_end_secure(cache, saved_cred);
cachefiles_see_object(object, cachefiles_obj_see_lookup_failed);
fscache_caching_failed(cookie);
_debug("failed c=%08x o=%08x", cookie->debug_id, object->debug_id);
/* The caller holds an access count on the cookie, so we need them to
* drop it before we can withdraw the object.
*/
return false;

fail_put:
cachefiles_put_object(object, cachefiles_obj_put_alloc_fail);
fail:
return false;
}

/*
* Commit changes to the object as we drop it.
*/
static void cachefiles_commit_object(struct cachefiles_object *object,
struct cachefiles_cache *cache)
{
bool update = false;

if (test_and_clear_bit(FSCACHE_COOKIE_LOCAL_WRITE, &object->cookie->flags))
update = true;
if (test_and_clear_bit(FSCACHE_COOKIE_NEEDS_UPDATE, &object->cookie->flags))
update = true;
if (update)
cachefiles_set_object_xattr(object);

if (test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags))
cachefiles_commit_tmpfile(cache, object);
}

/*
* Finalise and object and close the VFS structs that we have.
*/
static void cachefiles_clean_up_object(struct cachefiles_object *object,
struct cachefiles_cache *cache)
{
if (test_bit(FSCACHE_COOKIE_RETIRED, &object->cookie->flags)) {
if (!test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags)) {
cachefiles_see_object(object, cachefiles_obj_see_clean_delete);
_debug("- inval object OBJ%x", object->debug_id);
cachefiles_delete_object(object, FSCACHE_OBJECT_WAS_RETIRED);
} else {
cachefiles_see_object(object, cachefiles_obj_see_clean_drop_tmp);
_debug("- inval object OBJ%x tmpfile", object->debug_id);
}
} else {
cachefiles_see_object(object, cachefiles_obj_see_clean_commit);
cachefiles_commit_object(object, cache);
}

cachefiles_unmark_inode_in_use(object, object->file);
if (object->file) {
fput(object->file);
object->file = NULL;
}
}

/*
* Withdraw caching for a cookie.
*/
static void cachefiles_withdraw_cookie(struct fscache_cookie *cookie)
{
struct cachefiles_object *object = cookie->cache_priv;
struct cachefiles_cache *cache = object->volume->cache;
const struct cred *saved_cred;

_enter("o=%x", object->debug_id);
cachefiles_see_object(object, cachefiles_obj_see_withdraw_cookie);

if (!list_empty(&object->cache_link)) {
spin_lock(&cache->object_list_lock);
cachefiles_see_object(object, cachefiles_obj_see_withdrawal);
list_del_init(&object->cache_link);
spin_unlock(&cache->object_list_lock);
}

if (object->file) {
cachefiles_begin_secure(cache, &saved_cred);
cachefiles_clean_up_object(object, cache);
cachefiles_end_secure(cache, saved_cred);
}

cookie->cache_priv = NULL;
cachefiles_put_object(object, cachefiles_obj_put_detach);
}

/*
* Invalidate the storage associated with a cookie.
*/
static bool cachefiles_invalidate_cookie(struct fscache_cookie *cookie)
{
struct cachefiles_object *object = cookie->cache_priv;
struct file *new_file, *old_file;
bool old_tmpfile;

_enter("o=%x,[%llu]", object->debug_id, object->cookie->object_size);

old_tmpfile = test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags);

if (!object->file) {
fscache_resume_after_invalidation(cookie);
_leave(" = t [light]");
return true;
}

new_file = cachefiles_create_tmpfile(object);
if (IS_ERR(new_file))
goto failed;

/* Substitute the VFS target */
_debug("sub");
spin_lock(&object->lock);

old_file = object->file;
object->file = new_file;
object->content_info = CACHEFILES_CONTENT_NO_DATA;
set_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags);
set_bit(FSCACHE_COOKIE_NEEDS_UPDATE, &object->cookie->flags);

spin_unlock(&object->lock);
_debug("subbed");

/* Allow I/O to take place again */
fscache_resume_after_invalidation(cookie);

if (old_file) {
if (!old_tmpfile) {
struct cachefiles_volume *volume = object->volume;
struct dentry *fan = volume->fanout[(u8)cookie->key_hash];

inode_lock_nested(d_inode(fan), I_MUTEX_PARENT);
cachefiles_bury_object(volume->cache, object, fan,
old_file->f_path.dentry,
FSCACHE_OBJECT_INVALIDATED);
}
fput(old_file);
}

_leave(" = t");
return true;

failed:
_leave(" = f");
return false;
}

const struct fscache_cache_ops cachefiles_cache_ops = {
.name = "cachefiles",
.acquire_volume = cachefiles_acquire_volume,
.free_volume = cachefiles_free_volume,
.lookup_cookie = cachefiles_lookup_cookie,
.withdraw_cookie = cachefiles_withdraw_cookie,
.invalidate_cookie = cachefiles_invalidate_cookie,
.prepare_to_write = cachefiles_prepare_to_write,
};
9 changes: 9 additions & 0 deletions fs/cachefiles/internal.h
Expand Up @@ -16,6 +16,8 @@
#include <linux/cred.h>
#include <linux/security.h>

#define CACHEFILES_DIO_BLOCK_SIZE 4096

struct cachefiles_cache;
struct cachefiles_object;

Expand Down Expand Up @@ -68,6 +70,7 @@ struct cachefiles_cache {
struct dentry *graveyard; /* directory into which dead objects go */
struct file *cachefilesd; /* manager daemon handle */
struct list_head volumes; /* List of volume objects */
struct list_head object_list; /* List of active objects */
spinlock_t object_list_lock; /* Lock for volumes and object_list */
const struct cred *cache_cred; /* security override for accessing cache */
struct mutex daemon_mutex; /* command serialisation mutex */
Expand Down Expand Up @@ -194,6 +197,9 @@ extern int cachefiles_bury_object(struct cachefiles_cache *cache,
struct dentry *dir,
struct dentry *rep,
enum fscache_why_object_killed why);
extern int cachefiles_delete_object(struct cachefiles_object *object,
enum fscache_why_object_killed why);
extern bool cachefiles_look_up_object(struct cachefiles_object *object);
extern struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
struct dentry *dir,
const char *name,
Expand All @@ -205,6 +211,9 @@ extern int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir,

extern int cachefiles_check_in_use(struct cachefiles_cache *cache,
struct dentry *dir, char *filename);
extern struct file *cachefiles_create_tmpfile(struct cachefiles_object *object);
extern bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache,
struct cachefiles_object *object);

/*
* security.c
Expand Down

0 comments on commit 1f08c92

Please sign in to comment.