Merge patch series "Rework simple xattrs"

Christian Brauner (Amutable) <brauner@kernel.org> says:

Rework the simple xattrs api to make it more efficient and easier to
use for all consumers.

* patches from https://patch.msgid.link/20260605135322.2632068-1-mszeredi@redhat.com:
  simpe_xattr: use per-sb cache
  simple_xattr: change interface to pass struct simple_xattrs **
  tmpfs: simplify constructing "security.foo" xattr names
  kernfs: fix xattr race condition with multiple superblocks

Link: https://patch.msgid.link/20260605135322.2632068-1-mszeredi@redhat.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2026-06-06 15:21:46 +02:00
commit 3936c49a03
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
12 changed files with 254 additions and 273 deletions

View File

@ -605,11 +605,8 @@ void kernfs_put(struct kernfs_node *kn)
if (kernfs_type(kn) == KERNFS_LINK)
kernfs_put(kn->symlink.target_kn);
if (kn->iattr && kn->iattr->xattrs) {
simple_xattrs_free(kn->iattr->xattrs, NULL);
kfree(kn->iattr->xattrs);
kn->iattr->xattrs = NULL;
}
if (kn->iattr)
simple_xattrs_free(&root->xa_cache, &kn->iattr->xattrs, NULL);
spin_lock(&root->kernfs_idr_lock);
idr_remove(&root->ino_idr, (u32)kernfs_ino(kn));
@ -624,6 +621,7 @@ void kernfs_put(struct kernfs_node *kn)
} else {
/* just released the root kn, free @root too */
idr_destroy(&root->ino_idr);
simple_xattr_cache_cleanup(&root->xa_cache);
kfree_rcu(root, rcu);
}
}
@ -709,10 +707,7 @@ static struct kernfs_node *__kernfs_new_node(struct kernfs_root *root,
err_out4:
if (kn->iattr) {
if (kn->iattr->xattrs) {
simple_xattrs_free(kn->iattr->xattrs, NULL);
kfree(kn->iattr->xattrs);
}
simple_xattrs_free(&root->xa_cache, &kn->iattr->xattrs, NULL);
kmem_cache_free(kernfs_iattrs_cache, kn->iattr);
}
err_out3:

View File

@ -40,22 +40,15 @@ struct kernfs_open_node {
static DEFINE_SPINLOCK(kernfs_notify_lock);
static struct kernfs_node *kernfs_notify_list = KERNFS_NOTIFY_EOL;
/* Compatibility wrappers - use the common hashed node lock */
static inline struct mutex *kernfs_open_file_mutex_ptr(struct kernfs_node *kn)
{
int idx = hash_ptr(kn, NR_KERNFS_LOCK_BITS);
return &kernfs_locks->open_file_mutex[idx];
return kernfs_node_lock_ptr(kn);
}
static inline struct mutex *kernfs_open_file_mutex_lock(struct kernfs_node *kn)
{
struct mutex *lock;
lock = kernfs_open_file_mutex_ptr(kn);
mutex_lock(lock);
return lock;
return kernfs_node_lock(kn);
}
/**

View File

@ -37,6 +37,7 @@ static struct kernfs_iattrs *__kernfs_iattrs(struct kernfs_node *kn, bool alloc)
if (!ret)
return NULL;
INIT_LIST_HEAD_RCU(&ret->xattrs);
/* assign default attributes */
ret->ia_uid = GLOBAL_ROOT_UID;
ret->ia_gid = GLOBAL_ROOT_GID;
@ -144,8 +145,7 @@ ssize_t kernfs_iop_listxattr(struct dentry *dentry, char *buf, size_t size)
if (!attrs)
return -ENOMEM;
return simple_xattr_list(d_inode(dentry), READ_ONCE(attrs->xattrs),
buf, size);
return simple_xattr_list(d_inode(dentry), &attrs->xattrs, buf, size);
}
static inline void set_default_inode_attr(struct inode *inode, umode_t mode)
@ -297,34 +297,35 @@ int kernfs_xattr_get(struct kernfs_node *kn, const char *name,
void *value, size_t size)
{
struct kernfs_iattrs *attrs = kernfs_iattrs_noalloc(kn);
struct simple_xattrs *xattrs;
struct simple_xattr_cache *cache = &kernfs_root(kn)->xa_cache;
if (!attrs)
return -ENODATA;
xattrs = READ_ONCE(attrs->xattrs);
if (!xattrs)
return -ENODATA;
return simple_xattr_get(xattrs, name, value, size);
return simple_xattr_get(cache, &attrs->xattrs, name, value, size);
}
int kernfs_xattr_set(struct kernfs_node *kn, const char *name,
const void *value, size_t size, int flags)
{
struct simple_xattr *old_xattr;
struct simple_xattrs *xattrs;
struct kernfs_iattrs *attrs;
struct simple_xattr_cache *cache = &kernfs_root(kn)->xa_cache;
attrs = kernfs_iattrs(kn);
if (!attrs)
return -ENOMEM;
xattrs = simple_xattrs_lazy_alloc(&attrs->xattrs, value, flags);
if (IS_ERR_OR_NULL(xattrs))
return PTR_ERR(xattrs);
/*
* Protect xattr modifications with the hashed per-node mutex.
* Multiple superblocks (with different namespaces) can share the same
* kernfs_node, so inode locking alone is insufficient. The hashed mutex
* ensures serialization of concurrent xattr operations on the same node,
* including the lazy allocation of the xattrs structure itself.
*/
CLASS(kernfs_node_lock, lock)(kn);
old_xattr = simple_xattr_set(xattrs, name, value, size, flags);
old_xattr = simple_xattr_set(cache, &attrs->xattrs, name, value, size, flags);
if (IS_ERR(old_xattr))
return PTR_ERR(old_xattr);
@ -362,7 +363,6 @@ static int kernfs_vfs_user_xattr_set(const struct xattr_handler *handler,
{
const char *full_name = xattr_full_name(handler, suffix);
struct kernfs_node *kn = inode->i_private;
struct simple_xattrs *xattrs;
struct kernfs_iattrs *attrs;
if (!(kernfs_root(kn)->flags & KERNFS_ROOT_SUPPORT_USER_XATTR))
@ -372,11 +372,11 @@ static int kernfs_vfs_user_xattr_set(const struct xattr_handler *handler,
if (!attrs)
return -ENOMEM;
xattrs = simple_xattrs_lazy_alloc(&attrs->xattrs, value, flags);
if (IS_ERR_OR_NULL(xattrs))
return PTR_ERR(xattrs);
/* See comment in kernfs_xattr_set() about locking. */
CLASS(kernfs_node_lock, lock)(kn);
return simple_xattr_set_limited(xattrs, &attrs->xattr_limits,
return simple_xattr_set_limited(&kernfs_root(kn)->xa_cache,
&attrs->xattrs, &attrs->xattr_limits,
full_name, value, size, flags);
}

View File

@ -26,7 +26,7 @@ struct kernfs_iattrs {
struct timespec64 ia_mtime;
struct timespec64 ia_ctime;
struct simple_xattrs *xattrs;
struct list_head xattrs;
struct simple_xattr_limits xattr_limits;
};
@ -54,6 +54,8 @@ struct kernfs_root {
rwlock_t kernfs_rename_lock;
struct rcu_head rcu;
struct simple_xattr_cache xa_cache;
};
/* +1 to avoid triggering overflow warning when negating it */
@ -211,4 +213,24 @@ extern const struct inode_operations kernfs_symlink_iops;
* kernfs locks
*/
extern struct kernfs_global_locks *kernfs_locks;
/* Hashed mutex helpers - protect per-node data structures */
static inline struct mutex *kernfs_node_lock_ptr(struct kernfs_node *kn)
{
int idx = hash_ptr(kn, NR_KERNFS_LOCK_BITS);
return &kernfs_locks->node_mutex[idx];
}
static inline struct mutex *kernfs_node_lock(struct kernfs_node *kn)
{
struct mutex *lock = kernfs_node_lock_ptr(kn);
mutex_lock(lock);
return lock;
}
DEFINE_CLASS(kernfs_node_lock, struct mutex *,
mutex_unlock(_T), kernfs_node_lock(kn), struct kernfs_node *kn)
#endif /* __KERNFS_INTERNAL_H */

View File

@ -446,7 +446,7 @@ static void __init kernfs_mutex_init(void)
int count;
for (count = 0; count < NR_KERNFS_LOCKS; count++)
mutex_init(&kernfs_locks->open_file_mutex[count]);
mutex_init(&kernfs_locks->node_mutex[count]);
}
static void __init kernfs_lock_init(void)

View File

@ -37,6 +37,8 @@ static struct kmem_cache *pidfs_attr_cachep __ro_after_init;
static struct path pidfs_root_path = {};
static struct simple_xattr_cache pidfs_xa_cache;
void pidfs_get_root(struct path *path)
{
*path = pidfs_root_path;
@ -96,7 +98,7 @@ static const struct rhashtable_params pidfs_ino_ht_params = {
* use file handles.
*/
struct pidfs_attr {
struct simple_xattrs *xattrs;
struct list_head xattrs;
union {
struct pidfs_anon_attr;
struct llist_node pidfs_llist;
@ -196,12 +198,7 @@ static void pidfs_free_attr_work(struct work_struct *work)
head = llist_del_all(&pidfs_free_list);
llist_for_each_entry_safe(attr, next, head, pidfs_llist) {
struct simple_xattrs *xattrs = attr->xattrs;
if (xattrs) {
simple_xattrs_free(xattrs, NULL);
kfree(xattrs);
}
simple_xattrs_free(&pidfs_xa_cache, &attr->xattrs, NULL);
kfree(attr);
}
}
@ -229,7 +226,7 @@ void pidfs_free_pid(struct pid *pid)
if (IS_ERR(attr))
return;
if (likely(!attr->xattrs))
if (likely(list_empty(&attr->xattrs)))
kfree(attr);
else if (llist_add(&attr->pidfs_llist, &pidfs_free_list))
schedule_work(&pidfs_free_work);
@ -815,14 +812,8 @@ static ssize_t pidfs_listxattr(struct dentry *dentry, char *buf, size_t size)
{
struct inode *inode = d_inode(dentry);
struct pid *pid = inode->i_private;
struct pidfs_attr *attr = pid->attr;
struct simple_xattrs *xattrs;
xattrs = READ_ONCE(attr->xattrs);
if (!xattrs)
return 0;
return simple_xattr_list(inode, xattrs, buf, size);
return simple_xattr_list(inode, &pid->attr->xattrs, buf, size);
}
static const struct inode_operations pidfs_inode_operations = {
@ -1018,6 +1009,8 @@ int pidfs_register_pid(struct pid *pid)
if (!new_attr)
return -ENOMEM;
INIT_LIST_HEAD_RCU(&new_attr->xattrs);
/* Synchronize with pidfs_exit(). */
guard(spinlock_irq)(&pid->wait_pidfd.lock);
@ -1057,16 +1050,9 @@ static int pidfs_xattr_get(const struct xattr_handler *handler,
const char *suffix, void *value, size_t size)
{
struct pid *pid = inode->i_private;
struct pidfs_attr *attr = pid->attr;
const char *name;
struct simple_xattrs *xattrs;
const char *name = xattr_full_name(handler, suffix);
xattrs = READ_ONCE(attr->xattrs);
if (!xattrs)
return -ENODATA;
name = xattr_full_name(handler, suffix);
return simple_xattr_get(xattrs, name, value, size);
return simple_xattr_get(&pidfs_xa_cache, &pid->attr->xattrs, name, value, size);
}
static int pidfs_xattr_set(const struct xattr_handler *handler,
@ -1075,20 +1061,13 @@ static int pidfs_xattr_set(const struct xattr_handler *handler,
const void *value, size_t size, int flags)
{
struct pid *pid = inode->i_private;
struct pidfs_attr *attr = pid->attr;
const char *name;
struct simple_xattrs *xattrs;
const char *name = xattr_full_name(handler, suffix);
struct simple_xattr *old_xattr;
/* Ensure we're the only one to set @attr->xattrs. */
WARN_ON_ONCE(!inode_is_locked(inode));
xattrs = simple_xattrs_lazy_alloc(&attr->xattrs, value, flags);
if (IS_ERR_OR_NULL(xattrs))
return PTR_ERR(xattrs);
name = xattr_full_name(handler, suffix);
old_xattr = simple_xattr_set(xattrs, name, value, size, flags);
old_xattr = simple_xattr_set(&pidfs_xa_cache, &pid->attr->xattrs, name, value, size, flags);
if (IS_ERR(old_xattr))
return PTR_ERR(old_xattr);

View File

@ -28,6 +28,11 @@
#include "internal.h"
struct sx_key {
const struct list_head *parent;
const char *name;
};
static const char *
strcmp_prefix(const char *a, const char *a_prefix)
{
@ -1269,23 +1274,32 @@ struct simple_xattr *simple_xattr_alloc(const void *value, size_t size)
return new_xattr;
}
static u32 sx_hashfn(const char *name, const struct list_head *parent, u32 seed)
{
return jhash(name, strlen(name), jhash(&parent, sizeof(parent), seed));
}
static u32 simple_xattr_hashfn(const void *data, u32 len, u32 seed)
{
const char *name = data;
return jhash(name, strlen(name), seed);
const struct sx_key *key = data;
return sx_hashfn(key->name, key->parent, seed);
}
static u32 simple_xattr_obj_hashfn(const void *obj, u32 len, u32 seed)
{
const struct simple_xattr *xattr = obj;
return jhash(xattr->name, strlen(xattr->name), seed);
return sx_hashfn(xattr->name, xattr->parent, seed);
}
static int simple_xattr_obj_cmpfn(struct rhashtable_compare_arg *arg,
const void *obj)
{
const struct simple_xattr *xattr = obj;
return strcmp(xattr->name, arg->key);
const struct sx_key *key = arg->key;
return xattr->parent != key->parent || strcmp(xattr->name, key->name);
}
static const struct rhashtable_params simple_xattr_params = {
@ -1298,6 +1312,7 @@ static const struct rhashtable_params simple_xattr_params = {
/**
* simple_xattr_get - get an xattr object
* @cache: anchor for the hash table
* @xattrs: the header of the xattr object
* @name: the name of the xattr to retrieve
* @buffer: the buffer to store the value into
@ -1311,14 +1326,19 @@ static const struct rhashtable_params simple_xattr_params = {
* Return: On success the length of the xattr value is returned. On error a
* negative error code is returned.
*/
int simple_xattr_get(struct simple_xattrs *xattrs, const char *name,
void *buffer, size_t size)
int simple_xattr_get(struct simple_xattr_cache *cache, struct list_head *xattrs,
const char *name, void *buffer, size_t size)
{
struct simple_xattr *xattr;
struct sx_key key = { .parent = xattrs, .name = name };
struct rhashtable *ht = READ_ONCE(cache->ht);
int ret = -ENODATA;
if (!ht)
return ret;
guard(rcu)();
xattr = rhashtable_lookup(&xattrs->ht, name, simple_xattr_params);
xattr = rhashtable_lookup(ht, &key, simple_xattr_params);
if (xattr) {
ret = xattr->size;
if (buffer) {
@ -1331,8 +1351,45 @@ int simple_xattr_get(struct simple_xattrs *xattrs, const char *name,
return ret;
}
static struct rhashtable *simple_xattrs_lazy_alloc(struct simple_xattr_cache *cache,
const void *value, int flags)
{
struct rhashtable *oldht, *ht = READ_ONCE(cache->ht);
int err;
if (unlikely(!ht)) {
if (!value)
return (flags & XATTR_REPLACE) ? ERR_PTR(-ENODATA) : NULL;
ht = kzalloc_obj(*ht);
if (!ht)
return ERR_PTR(-ENOMEM);
err = rhashtable_init(ht, &simple_xattr_params);
if (err) {
kfree(ht);
return ERR_PTR(err);
}
/*
* Provides release semantics on success, so that use of a
* non-NULL READ_ONCE(cache->ht) will be ordered relative to the
* above initialization, due to implicit address dependency.
*/
oldht = cmpxchg_release(&cache->ht, NULL, ht);
if (oldht) {
/* Race lost */
rhashtable_destroy(ht);
kfree(ht);
ht = oldht;
}
}
return ht;
}
/**
* simple_xattr_set - set an xattr object
* @cache: anchor for the hash table
* @xattrs: the header of the xattr object
* @name: the name of the xattr to retrieve
* @value: the value to store along the xattr
@ -1362,45 +1419,58 @@ int simple_xattr_get(struct simple_xattrs *xattrs, const char *name,
* Return: On success, the removed or replaced xattr is returned, to be freed
* by the caller; or NULL if none. On failure a negative error code is returned.
*/
struct simple_xattr *simple_xattr_set(struct simple_xattrs *xattrs,
struct simple_xattr *simple_xattr_set(struct simple_xattr_cache *cache, struct list_head *xattrs,
const char *name, const void *value,
size_t size, int flags)
{
struct sx_key key = { .parent = xattrs, .name = name };
struct simple_xattr *old_xattr = NULL;
struct rhashtable *ht;
int err;
ht = simple_xattrs_lazy_alloc(cache, value, flags);
if (IS_ERR_OR_NULL(ht))
return ERR_CAST(ht);
CLASS(simple_xattr, new_xattr)(value, size);
if (IS_ERR(new_xattr))
return new_xattr;
if (new_xattr) {
new_xattr->parent = xattrs;
new_xattr->name = kstrdup(name, GFP_KERNEL_ACCOUNT);
if (!new_xattr->name)
return ERR_PTR(-ENOMEM);
}
/* Lookup is safe without RCU here since writes are serialized. */
old_xattr = rhashtable_lookup_fast(&xattrs->ht, name,
simple_xattr_params);
/*
* Hash table lookup/replace/remove will grab RCU read lock themselves.
* This makes sure that hash table lookup is safe against concurrent
* modification on another inode.
*/
old_xattr = rhashtable_lookup_fast(ht, &key, simple_xattr_params);
if (old_xattr) {
/* Fail if XATTR_CREATE is requested and the xattr exists. */
if (flags & XATTR_CREATE)
return ERR_PTR(-EEXIST);
if (new_xattr) {
err = rhashtable_replace_fast(&xattrs->ht,
err = rhashtable_replace_fast(ht,
&old_xattr->hash_node,
&new_xattr->hash_node,
simple_xattr_params);
if (err)
return ERR_PTR(err);
list_replace_rcu(&old_xattr->node, &new_xattr->node);
} else {
err = rhashtable_remove_fast(&xattrs->ht,
err = rhashtable_remove_fast(ht,
&old_xattr->hash_node,
simple_xattr_params);
if (err)
return ERR_PTR(err);
list_del_rcu(&old_xattr->node);
}
} else {
/* Fail if XATTR_REPLACE is requested but no xattr is found. */
@ -1412,11 +1482,13 @@ struct simple_xattr *simple_xattr_set(struct simple_xattrs *xattrs,
* new value simply insert it.
*/
if (new_xattr) {
err = rhashtable_insert_fast(&xattrs->ht,
err = rhashtable_insert_fast(ht,
&new_xattr->hash_node,
simple_xattr_params);
if (err)
return ERR_PTR(err);
list_add_tail_rcu(&new_xattr->node, xattrs);
}
/*
@ -1453,6 +1525,7 @@ static inline int simple_xattr_limits_inc(struct simple_xattr_limits *limits,
/**
* simple_xattr_set_limited - set an xattr with per-inode user.* limits
* @cache: anchor for the hash table
* @xattrs: the header of the xattr object
* @limits: per-inode limit counters for user.* xattrs
* @name: the name of the xattr to set or remove
@ -1467,7 +1540,7 @@ static inline int simple_xattr_limits_inc(struct simple_xattr_limits *limits,
* Return: On success zero is returned. On failure a negative error code is
* returned.
*/
int simple_xattr_set_limited(struct simple_xattrs *xattrs,
int simple_xattr_set_limited(struct simple_xattr_cache *cache, struct list_head *xattrs,
struct simple_xattr_limits *limits,
const char *name, const void *value,
size_t size, int flags)
@ -1481,7 +1554,7 @@ int simple_xattr_set_limited(struct simple_xattrs *xattrs,
return ret;
}
old_xattr = simple_xattr_set(xattrs, name, value, size, flags);
old_xattr = simple_xattr_set(cache, xattrs, name, value, size, flags);
if (IS_ERR(old_xattr)) {
if (value)
simple_xattr_limits_dec(limits, size);
@ -1527,11 +1600,10 @@ static bool xattr_is_maclabel(const char *name)
* Return: On success the required size or the size of the copied xattrs is
* returned. On error a negative error code is returned.
*/
ssize_t simple_xattr_list(struct inode *inode, struct simple_xattrs *xattrs,
ssize_t simple_xattr_list(struct inode *inode, struct list_head *xattrs,
char *buffer, size_t size)
{
bool trusted = ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN);
struct rhashtable_iter iter;
struct simple_xattr *xattr;
ssize_t remaining_size = size;
int err = 0;
@ -1555,17 +1627,8 @@ ssize_t simple_xattr_list(struct inode *inode, struct simple_xattrs *xattrs,
if (!xattrs)
return size - remaining_size;
rhashtable_walk_enter(&xattrs->ht, &iter);
rhashtable_walk_start(&iter);
while ((xattr = rhashtable_walk_next(&iter)) != NULL) {
if (IS_ERR(xattr)) {
if (PTR_ERR(xattr) == -EAGAIN)
continue;
err = PTR_ERR(xattr);
break;
}
rcu_read_lock();
list_for_each_entry_rcu(xattr, xattrs, node) {
/* skip "trusted." attributes for unprivileged callers */
if (!trusted && xattr_is_trusted(xattr->name))
continue;
@ -1578,15 +1641,14 @@ ssize_t simple_xattr_list(struct inode *inode, struct simple_xattrs *xattrs,
if (err)
break;
}
rhashtable_walk_stop(&iter);
rhashtable_walk_exit(&iter);
rcu_read_unlock();
return err ? err : size - remaining_size;
}
/**
* simple_xattr_add - add xattr objects
* @cache: anchor for the hash table
* @xattrs: the header of the xattr object
* @new_xattr: the xattr object to add
*
@ -1597,112 +1659,67 @@ ssize_t simple_xattr_list(struct inode *inode, struct simple_xattrs *xattrs,
* Return: On success zero is returned. On failure a negative error code is
* returned.
*/
int simple_xattr_add(struct simple_xattrs *xattrs,
int simple_xattr_add(struct simple_xattr_cache *cache, struct list_head *xattrs,
struct simple_xattr *new_xattr)
{
return rhashtable_insert_fast(&xattrs->ht, &new_xattr->hash_node,
simple_xattr_params);
}
struct rhashtable *ht;
int err;
/**
* simple_xattrs_init - initialize new xattr header
* @xattrs: header to initialize
*
* Initialize the rhashtable used to store xattr objects.
*
* Return: On success zero is returned. On failure a negative error code is
* returned.
*/
int simple_xattrs_init(struct simple_xattrs *xattrs)
{
return rhashtable_init(&xattrs->ht, &simple_xattr_params);
}
ht = simple_xattrs_lazy_alloc(cache, new_xattr->value, 0);
if (IS_ERR(ht))
return PTR_ERR(ht);
/**
* simple_xattrs_alloc - allocate and initialize a new xattr header
*
* Dynamically allocate a simple_xattrs header and initialize the
* underlying rhashtable. This is intended for consumers that want
* to lazily allocate xattr storage only when the first xattr is set,
* avoiding the per-inode rhashtable overhead when no xattrs are used.
*
* Return: On success a new simple_xattrs is returned. On failure an
* ERR_PTR is returned.
*/
struct simple_xattrs *simple_xattrs_alloc(void)
{
struct simple_xattrs *xattrs __free(kfree) = NULL;
int ret;
new_xattr->parent = xattrs;
err = rhashtable_insert_fast(ht, &new_xattr->hash_node, simple_xattr_params);
if (err)
return err;
xattrs = kzalloc(sizeof(*xattrs), GFP_KERNEL);
if (!xattrs)
return ERR_PTR(-ENOMEM);
ret = simple_xattrs_init(xattrs);
if (ret)
return ERR_PTR(ret);
return no_free_ptr(xattrs);
}
/**
* simple_xattrs_lazy_alloc - get or allocate xattrs for a set operation
* @xattrsp: pointer to the xattrs pointer (may point to NULL)
* @value: value being set (NULL means remove)
* @flags: xattr set flags
*
* For lazily-allocated xattrs on the write path. If no xattrs exist yet
* and this is a remove operation, returns the appropriate result without
* allocating. Otherwise ensures xattrs is allocated and published with
* store-release semantics.
*
* Return: On success a valid pointer to the xattrs is returned. On
* failure or early-exit an ERR_PTR or NULL is returned. Callers should
* check with IS_ERR_OR_NULL() and propagate with PTR_ERR() which
* correctly returns 0 for the NULL no-op case.
*/
struct simple_xattrs *simple_xattrs_lazy_alloc(struct simple_xattrs **xattrsp,
const void *value, int flags)
{
struct simple_xattrs *xattrs;
xattrs = READ_ONCE(*xattrsp);
if (xattrs)
return xattrs;
if (!value)
return (flags & XATTR_REPLACE) ? ERR_PTR(-ENODATA) : NULL;
xattrs = simple_xattrs_alloc();
if (!IS_ERR(xattrs))
smp_store_release(xattrsp, xattrs);
return xattrs;
}
static void simple_xattr_ht_free(void *ptr, void *arg)
{
struct simple_xattr *xattr = ptr;
size_t *freed_space = arg;
if (freed_space)
*freed_space += simple_xattr_space(xattr->name, xattr->size);
simple_xattr_free(xattr);
list_add_tail_rcu(&new_xattr->node, xattrs);
return 0;
}
/**
* simple_xattrs_free - free xattrs
* @cache: anchor for the hash table
* @xattrs: xattr header whose xattrs to destroy
* @freed_space: approximate number of bytes of memory freed from @xattrs
*
* Destroy all xattrs in @xattr. When this is called no one can hold a
* Destroy all xattrs in @xattrs. When this is called no one can hold a
* reference to any of the xattrs anymore.
*/
void simple_xattrs_free(struct simple_xattrs *xattrs, size_t *freed_space)
void simple_xattrs_free(struct simple_xattr_cache *cache, struct list_head *xattrs,
size_t *freed_space)
{
might_sleep();
if (freed_space)
*freed_space = 0;
rhashtable_free_and_destroy(&xattrs->ht, simple_xattr_ht_free,
freed_space);
while (!list_empty(xattrs)) {
struct simple_xattr *xattr = list_first_entry(xattrs, typeof(*xattr), node);
rhashtable_remove_fast(cache->ht, &xattr->hash_node, simple_xattr_params);
list_del(&xattr->node);
if (freed_space)
*freed_space += simple_xattr_space(xattr->name, xattr->size);
/*
* Free with RCU, since the xattr might still get accessed by
* the hash compare function
*/
simple_xattr_free_rcu(xattr);
}
}
/**
* simple_xattr_cache_cleanup - free the cache
* @cache: anchor for the hash table
*
* Destroy the cache table, which was lazily allocated on adding the first xattr.
*/
void simple_xattr_cache_cleanup(struct simple_xattr_cache *cache)
{
if (cache->ht) {
WARN_ON(atomic_read(&cache->ht->nelems));
rhashtable_destroy(cache->ht);
kfree(cache->ht);
cache->ht = NULL;
}
}

View File

@ -76,20 +76,25 @@ struct kernfs_iattrs;
* kernfs_open_file.
*
* kernfs_open_files are chained at kernfs_open_node->files, which is
* protected by kernfs_global_locks.open_file_mutex[i].
* protected by kernfs_global_locks.node_mutex[i].
*
* To reduce possible contention in sysfs access, arising due to single
* locks, use an array of locks (e.g. open_file_mutex) and use kernfs_node
* locks, use an array of locks (e.g. node_mutex) and use kernfs_node
* object address as hash keys to get the index of these locks.
*
* Hashed mutexes are safe to use here because operations using these don't
* rely on global exclusion.
*
* The hashed mutex array protects per-node data: the kernfs_open_node for
* open file management, and kernfs_node xattr operations (necessary because
* multiple superblocks with different namespaces can share the same
* kernfs_node, making per-inode locking insufficient).
*
* In future we intend to replace other global locks with hashed ones as well.
* kernfs_global_locks acts as a holder for all such hash tables.
*/
struct kernfs_global_locks {
struct mutex open_file_mutex[NR_KERNFS_LOCKS];
struct mutex node_mutex[NR_KERNFS_LOCKS];
};
enum kernfs_node_type {

View File

@ -48,7 +48,7 @@ struct shmem_inode_info {
};
struct timespec64 i_crtime; /* file creation time */
struct shared_policy policy; /* NUMA memory alloc policy */
struct simple_xattrs *xattrs; /* list of xattrs */
struct list_head xattrs; /* list of xattrs */
pgoff_t fallocend; /* highest fallocate endindex */
unsigned int fsflags; /* for FS_IOC_[SG]ETFLAGS */
atomic_t stop_eviction; /* hold when working on inode */
@ -89,6 +89,7 @@ struct shmem_sb_info {
struct list_head shrinklist; /* List of shinkable inodes */
unsigned long shrinklist_len; /* Length of shrinklist */
struct shmem_quota_limits qlimits; /* Default quota limits */
struct simple_xattr_cache xa_cache;
};
static inline struct shmem_inode_info *SHMEM_I(struct inode *inode)

View File

@ -106,12 +106,14 @@ static inline const char *xattr_prefix(const struct xattr_handler *handler)
return handler->prefix ?: handler->name;
}
struct simple_xattrs {
struct rhashtable ht;
struct simple_xattr_cache {
struct rhashtable *ht;
};
struct simple_xattr {
struct rhash_head hash_node;
struct list_head *parent;
struct list_head node;
struct rcu_head rcu;
char *name;
size_t size;
@ -132,40 +134,35 @@ static inline void simple_xattr_limits_init(struct simple_xattr_limits *limits)
atomic_set(&limits->xattr_size, 0);
}
int simple_xattrs_init(struct simple_xattrs *xattrs);
struct simple_xattrs *simple_xattrs_alloc(void);
struct simple_xattrs *simple_xattrs_lazy_alloc(struct simple_xattrs **xattrsp,
const void *value, int flags);
void simple_xattrs_free(struct simple_xattrs *xattrs, size_t *freed_space);
void simple_xattrs_free(struct simple_xattr_cache *cache, struct list_head *xattrs,
size_t *freed_space);
size_t simple_xattr_space(const char *name, size_t size);
struct simple_xattr *simple_xattr_alloc(const void *value, size_t size);
void simple_xattr_free(struct simple_xattr *xattr);
void simple_xattr_free_rcu(struct simple_xattr *xattr);
int simple_xattr_get(struct simple_xattrs *xattrs, const char *name,
void *buffer, size_t size);
struct simple_xattr *simple_xattr_set(struct simple_xattrs *xattrs,
int simple_xattr_get(struct simple_xattr_cache *cache, struct list_head *xattrs,
const char *name, void *buffer, size_t size);
struct simple_xattr *simple_xattr_set(struct simple_xattr_cache *cache,
struct list_head *xattrs,
const char *name, const void *value,
size_t size, int flags);
int simple_xattr_set_limited(struct simple_xattrs *xattrs,
int simple_xattr_set_limited(struct simple_xattr_cache *cache,
struct list_head *xattrs,
struct simple_xattr_limits *limits,
const char *name, const void *value,
size_t size, int flags);
ssize_t simple_xattr_list(struct inode *inode, struct simple_xattrs *xattrs,
ssize_t simple_xattr_list(struct inode *inode, struct list_head *xattrs,
char *buffer, size_t size);
int simple_xattr_add(struct simple_xattrs *xattrs,
int simple_xattr_add(struct simple_xattr_cache *cache, struct list_head *xattrs,
struct simple_xattr *new_xattr);
int xattr_list_one(char **buffer, ssize_t *remaining_size, const char *name);
void simple_xattr_cache_cleanup(struct simple_xattr_cache *cache);
DEFINE_CLASS(simple_xattr,
struct simple_xattr *,
if (!IS_ERR_OR_NULL(_T)) simple_xattr_free(_T),
simple_xattr_alloc(value, size),
const void *value, size_t size)
DEFINE_CLASS(simple_xattrs,
struct simple_xattrs *,
if (!IS_ERR_OR_NULL(_T)) { simple_xattrs_free(_T, NULL); kfree(_T); },
simple_xattrs_alloc(),
void)
#endif /* _LINUX_XATTR_H */

View File

@ -1425,10 +1425,8 @@ static void shmem_evict_inode(struct inode *inode)
}
}
if (info->xattrs) {
simple_xattrs_free(info->xattrs, sbinfo->max_inodes ? &freed : NULL);
kfree(info->xattrs);
}
simple_xattrs_free(&sbinfo->xa_cache, &info->xattrs, sbinfo->max_inodes ? &freed : NULL);
shmem_free_inode(inode->i_sb, freed);
WARN_ON(inode->i_blocks);
clear_inode(inode);
@ -3086,6 +3084,7 @@ static struct inode *__shmem_get_inode(struct mnt_idmap *idmap,
inode->i_generation = get_random_u32();
info = SHMEM_I(inode);
memset(info, 0, (char *)inode - (char *)info);
INIT_LIST_HEAD_RCU(&info->xattrs);
spin_lock_init(&info->lock);
atomic_set(&info->stop_eviction, 0);
info->seals = F_SEAL_SEAL;
@ -4232,11 +4231,6 @@ static int shmem_initxattrs(struct inode *inode,
struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
const struct xattr *xattr;
size_t ispace = 0;
size_t len;
CLASS(simple_xattrs, xattrs)();
if (IS_ERR(xattrs))
return PTR_ERR(xattrs);
if (sbinfo->max_inodes) {
for (xattr = xattr_array; xattr->name != NULL; xattr++) {
@ -4260,19 +4254,16 @@ static int shmem_initxattrs(struct inode *inode,
if (IS_ERR(new_xattr))
break;
len = strlen(xattr->name) + 1;
new_xattr->name = kmalloc(XATTR_SECURITY_PREFIX_LEN + len,
GFP_KERNEL_ACCOUNT);
new_xattr->name = kasprintf(GFP_KERNEL_ACCOUNT,
XATTR_SECURITY_PREFIX "%s", xattr->name);
if (!new_xattr->name)
break;
memcpy(new_xattr->name, XATTR_SECURITY_PREFIX,
XATTR_SECURITY_PREFIX_LEN);
memcpy(new_xattr->name + XATTR_SECURITY_PREFIX_LEN,
xattr->name, len);
if (simple_xattr_add(xattrs, new_xattr))
if (simple_xattr_add(&sbinfo->xa_cache, &info->xattrs, new_xattr))
break;
if (sbinfo->max_inodes)
ispace -= simple_xattr_space(new_xattr->name, new_xattr->size);
retain_and_null_ptr(new_xattr);
}
@ -4284,8 +4275,8 @@ static int shmem_initxattrs(struct inode *inode,
}
return -ENOMEM;
}
WARN_ON(ispace);
smp_store_release(&info->xattrs, no_free_ptr(xattrs));
return 0;
}
@ -4293,15 +4284,11 @@ static int shmem_xattr_handler_get(const struct xattr_handler *handler,
struct dentry *unused, struct inode *inode,
const char *name, void *buffer, size_t size)
{
struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
struct shmem_inode_info *info = SHMEM_I(inode);
struct simple_xattrs *xattrs;
xattrs = READ_ONCE(info->xattrs);
if (!xattrs)
return -ENODATA;
name = xattr_full_name(handler, name);
return simple_xattr_get(xattrs, name, buffer, size);
return simple_xattr_get(&sbinfo->xa_cache, &info->xattrs, name, buffer, size);
}
static int shmem_xattr_handler_set(const struct xattr_handler *handler,
@ -4312,16 +4299,11 @@ static int shmem_xattr_handler_set(const struct xattr_handler *handler,
{
struct shmem_inode_info *info = SHMEM_I(inode);
struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
struct simple_xattrs *xattrs;
struct simple_xattr *old_xattr;
size_t ispace = 0;
name = xattr_full_name(handler, name);
xattrs = simple_xattrs_lazy_alloc(&info->xattrs, value, flags);
if (IS_ERR_OR_NULL(xattrs))
return PTR_ERR(xattrs);
if (value && sbinfo->max_inodes) {
ispace = simple_xattr_space(name, size);
raw_spin_lock(&sbinfo->stat_lock);
@ -4334,7 +4316,7 @@ static int shmem_xattr_handler_set(const struct xattr_handler *handler,
return -ENOSPC;
}
old_xattr = simple_xattr_set(xattrs, name, value, size, flags);
old_xattr = simple_xattr_set(&sbinfo->xa_cache, &info->xattrs, name, value, size, flags);
if (!IS_ERR(old_xattr)) {
ispace = 0;
if (old_xattr && sbinfo->max_inodes)
@ -4382,8 +4364,7 @@ static ssize_t shmem_listxattr(struct dentry *dentry, char *buffer, size_t size)
{
struct shmem_inode_info *info = SHMEM_I(d_inode(dentry));
return simple_xattr_list(d_inode(dentry), READ_ONCE(info->xattrs),
buffer, size);
return simple_xattr_list(d_inode(dentry), &info->xattrs, buffer, size);
}
#endif /* CONFIG_TMPFS_XATTR */
@ -4984,6 +4965,9 @@ static void shmem_put_super(struct super_block *sb)
free_percpu(sbinfo->ino_batch);
percpu_counter_destroy(&sbinfo->used_blocks);
mpol_put(sbinfo->mpol);
#ifdef CONFIG_TMPFS_XATTR
simple_xattr_cache_cleanup(&sbinfo->xa_cache);
#endif
kfree(sbinfo);
sb->s_fs_info = NULL;
}

View File

@ -310,8 +310,10 @@ static int move_addr_to_user(struct sockaddr_storage *kaddr, int klen,
static struct kmem_cache *sock_inode_cachep __ro_after_init;
static struct simple_xattr_cache sockfs_xa_cache;
struct sockfs_inode {
struct simple_xattrs *xattrs;
struct list_head xattrs;
struct simple_xattr_limits xattr_limits;
struct socket_alloc;
};
@ -328,7 +330,7 @@ static struct inode *sock_alloc_inode(struct super_block *sb)
si = alloc_inode_sb(sb, sock_inode_cachep, GFP_KERNEL);
if (!si)
return NULL;
si->xattrs = NULL;
INIT_LIST_HEAD_RCU(&si->xattrs);
simple_xattr_limits_init(&si->xattr_limits);
init_waitqueue_head(&si->socket.wq.wait);
@ -347,12 +349,8 @@ static struct inode *sock_alloc_inode(struct super_block *sb)
static void sock_evict_inode(struct inode *inode)
{
struct sockfs_inode *si = SOCKFS_I(inode);
struct simple_xattrs *xattrs = si->xattrs;
if (xattrs) {
simple_xattrs_free(xattrs, NULL);
kfree(xattrs);
}
simple_xattrs_free(&sockfs_xa_cache, &si->xattrs, NULL);
clear_inode(inode);
}
@ -443,13 +441,9 @@ static int sockfs_user_xattr_get(const struct xattr_handler *handler,
const char *suffix, void *value, size_t size)
{
const char *name = xattr_full_name(handler, suffix);
struct simple_xattrs *xattrs;
struct sockfs_inode *si = SOCKFS_I(inode);
xattrs = READ_ONCE(SOCKFS_I(inode)->xattrs);
if (!xattrs)
return -ENODATA;
return simple_xattr_get(xattrs, name, value, size);
return simple_xattr_get(&sockfs_xa_cache, &si->xattrs, name, value, size);
}
static int sockfs_user_xattr_set(const struct xattr_handler *handler,
@ -460,13 +454,8 @@ static int sockfs_user_xattr_set(const struct xattr_handler *handler,
{
const char *name = xattr_full_name(handler, suffix);
struct sockfs_inode *si = SOCKFS_I(inode);
struct simple_xattrs *xattrs;
xattrs = simple_xattrs_lazy_alloc(&si->xattrs, value, flags);
if (IS_ERR_OR_NULL(xattrs))
return PTR_ERR(xattrs);
return simple_xattr_set_limited(xattrs, &si->xattr_limits,
return simple_xattr_set_limited(&sockfs_xa_cache, &si->xattrs, &si->xattr_limits,
name, value, size, flags);
}
@ -635,8 +624,7 @@ static ssize_t sockfs_listxattr(struct dentry *dentry, char *buffer,
struct sockfs_inode *si = SOCKFS_I(d_inode(dentry));
ssize_t len, used;
len = simple_xattr_list(d_inode(dentry), READ_ONCE(si->xattrs),
buffer, size);
len = simple_xattr_list(d_inode(dentry), &si->xattrs, buffer, size);
if (len < 0)
return len;