// SPDX-License-Identifier: GPL-2.0-or-later
/* netfs cookie management
*
* Copyright (C) 2004-2007 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* See Documentation/filesystems/caching/netfs-api.rst for more information on
* the netfs API.
*/
#define FSCACHE_DEBUG_LEVEL COOKIE
#include <linux/module.h>
#include <linux/slab.h>
#include "internal.h"
struct kmem_cache *fscache_cookie_jar;
static atomic_t fscache_object_debug_id = ATOMIC_INIT(0);
#define fscache_cookie_hash_shift 15
static struct hlist_bl_head fscache_cookie_hash[1 << fscache_cookie_hash_shift];
static LIST_HEAD(fscache_cookies);
static DEFINE_RWLOCK(fscache_cookies_lock);
static int fscache_acquire_non_index_cookie(struct fscache_cookie *cookie,
loff_t object_size);
static int fscache_alloc_object(struct fscache_cache *cache,
struct fscache_cookie *cookie);
static int fscache_attach_object(struct fscache_cookie *cookie,
struct fscache_object *object);
static void fscache_print_cookie(struct fscache_cookie *cookie, char prefix)
{
struct fscache_object *object;
struct hlist_node *o;
const u8 *k;
unsigned loop;
pr_err("%c-cookie c=%08x [p=%08x fl=%lx nc=%u na=%u]\n",
prefix,
cookie->debug_id,
cookie->parent ? cookie->parent->debug_id : 0,
cookie->flags,
atomic_read(&cookie->n_children),
atomic_read(&cookie->n_active));
pr_err("%c-cookie d=%p{%s} n=%p\n",
prefix,
cookie->def,
cookie->def ? cookie->def->name : "?",
cookie->netfs_data);
o = READ_ONCE(cookie->backing_objects.first);
if (o) {
object = hlist_entry(o, struct fscache_object, cookie_link);
pr_err("%c-cookie o=%u\n", prefix, object->debug_id);
}
pr_err("%c-key=[%u] '", prefix, cookie->key_len);
k = (cookie->key_len <= sizeof(cookie->inline_key)) ?
cookie->inline_key : cookie->key;
for (loop = 0; loop < cookie->key_len; loop++)
pr_cont("%02x", k[loop]);
pr_cont("'\n");
}
void fscache_free_cookie(struct fscache_cookie *cookie)
{
if (cookie) {
BUG_ON(!hlist_empty(&cookie->backing_objects));
write_lock(&fscache_cookies_lock);
list_del(&cookie->proc_link);
write_unlock(&fscache_cookies_lock);
if (cookie->aux_len > sizeof(cookie->inline_aux))
kfree(cookie->aux);
if (cookie->key_len > sizeof(cookie->inline_key))
kfree(cookie->key);
kmem_cache_free(fscache_cookie_jar, cookie);
}
}
/*
* Set the index key in a cookie. The cookie struct has space for a 16-byte
* key plus length and hash, but if that's not big enough, it's instead a
* pointer to a buffer containing 3 bytes of hash, 1 byte of length and then
* the key data.
*/
static int fscache_set_key(struct fscache_cookie *cookie,
const void *index_key, size_t index_key_len)
{
unsigned long long h;
u32 *buf;
int bufs;
int i;
bufs = DIV_ROUND_UP(index_key_len, sizeof(*buf));
if (index_key_len > sizeof(cookie->inline_key)) {
buf = kcalloc(bufs, sizeof(*buf), GFP_KERNEL);
if (!buf)
return -ENOMEM;
cookie->key = buf;
} else {
buf = (u32 *)cookie->inline_key;
}
memcpy(buf, index_key, index_key_len);
/* Calculate a hash and combine this with the length in the first word
* or first half word
*/
h = (unsigned long)cookie->parent;
h += index_key_len + cookie->type;
for (i = 0; i < bufs; i++)
h += buf[i];
cookie->key_hash = h ^ (h >> 32);
return 0;
}
static long fscache_compare_cookie(const struct fscache_cookie *a,
const struct fscache_cookie *b)
{
const void *ka, *kb;
if (a->key_hash != b->key_hash)
return (long)a->key_hash - (long)b->key_hash;
if (a->parent != b->parent)
return (long)a->parent - (long)b->parent;
if (a->key_len != b->key_len)
return (long)a->key_len - (long)b->key_len;
if (a->type != b->type)
return (long)a->type - (long)b->type;
if (a->key_len <= sizeof(a->inline_key)) {
ka = &a->inline_key;
kb =