[PATCH v2 67/79] ssdfs: implement extended attributes support

Viacheslav Dubeyko posted 79 patches 3 weeks, 1 day ago
Only 33 patches received!
[PATCH v2 67/79] ssdfs: implement extended attributes support
Posted by Viacheslav Dubeyko 3 weeks, 1 day ago
Complete patchset is available here:
https://github.com/dubeyko/ssdfs-driver/tree/master/patchset/linux-kernel-6.18.0

This patch implements the extended attributes support.

Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
---
 fs/ssdfs/acl.c            |  260 ++++++
 fs/ssdfs/acl.h            |   54 ++
 fs/ssdfs/xattr.c          | 1700 +++++++++++++++++++++++++++++++++++++
 fs/ssdfs/xattr.h          |   88 ++
 fs/ssdfs/xattr_security.c |  159 ++++
 fs/ssdfs/xattr_tree.h     |  143 ++++
 fs/ssdfs/xattr_trusted.c  |   93 ++
 fs/ssdfs/xattr_user.c     |   93 ++
 8 files changed, 2590 insertions(+)
 create mode 100644 fs/ssdfs/acl.c
 create mode 100644 fs/ssdfs/acl.h
 create mode 100644 fs/ssdfs/xattr.c
 create mode 100644 fs/ssdfs/xattr.h
 create mode 100644 fs/ssdfs/xattr_security.c
 create mode 100644 fs/ssdfs/xattr_tree.h
 create mode 100644 fs/ssdfs/xattr_trusted.c
 create mode 100644 fs/ssdfs/xattr_user.c

diff --git a/fs/ssdfs/acl.c b/fs/ssdfs/acl.c
new file mode 100644
index 000000000000..04a075f4424d
--- /dev/null
+++ b/fs/ssdfs/acl.c
@@ -0,0 +1,260 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ *
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/acl.c - ACLs support implementation.
+ *
+ * Copyright (c) 2014-2019 HGST, a Western Digital Company.
+ *              http://www.hgst.com/
+ * Copyright (c) 2014-2026 Viacheslav Dubeyko <slava@dubeyko.com>
+ *              http://www.ssdfs.org/
+ *
+ * (C) Copyright 2014-2019, HGST, Inc., All rights reserved.
+ *
+ * Created by HGST, San Jose Research Center, Storage Architecture Group
+ *
+ * Authors: Viacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Acknowledgement: Cyril Guyot
+ *                  Zvonimir Bandic
+ */
+
+#include <linux/kernel.h>
+#include <linux/rwsem.h>
+#include <linux/pagevec.h>
+
+#include "peb_mapping_queue.h"
+#include "peb_mapping_table_cache.h"
+#include "folio_vector.h"
+#include "ssdfs.h"
+#include "xattr.h"
+#include "acl.h"
+
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+atomic64_t ssdfs_acl_folio_leaks;
+atomic64_t ssdfs_acl_memory_leaks;
+atomic64_t ssdfs_acl_cache_leaks;
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+
+/*
+ * void ssdfs_acl_cache_leaks_increment(void *kaddr)
+ * void ssdfs_acl_cache_leaks_decrement(void *kaddr)
+ * void *ssdfs_acl_kmalloc(size_t size, gfp_t flags)
+ * void *ssdfs_acl_kzalloc(size_t size, gfp_t flags)
+ * void *ssdfs_acl_kcalloc(size_t n, size_t size, gfp_t flags)
+ * void ssdfs_acl_kfree(void *kaddr)
+ * struct folio *ssdfs_acl_alloc_folio(gfp_t gfp_mask, unsigned int order)
+ * struct folio *ssdfs_acl_add_batch_folio(struct folio_batch *batch,
+ *                                         unsigned int order)
+ * void ssdfs_acl_free_folio(struct folio *folio)
+ * void ssdfs_acl_folio_batch_release(struct folio_batch *batch)
+ */
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+	SSDFS_MEMORY_LEAKS_CHECKER_FNS(acl)
+#else
+	SSDFS_MEMORY_ALLOCATOR_FNS(acl)
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+
+void ssdfs_acl_memory_leaks_init(void)
+{
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+	atomic64_set(&ssdfs_acl_folio_leaks, 0);
+	atomic64_set(&ssdfs_acl_memory_leaks, 0);
+	atomic64_set(&ssdfs_acl_cache_leaks, 0);
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+}
+
+void ssdfs_acl_check_memory_leaks(void)
+{
+#ifdef CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING
+	if (atomic64_read(&ssdfs_acl_folio_leaks) != 0) {
+		SSDFS_ERR("ACL: "
+			  "memory leaks include %lld folios\n",
+			  atomic64_read(&ssdfs_acl_folio_leaks));
+	}
+
+	if (atomic64_read(&ssdfs_acl_memory_leaks) != 0) {
+		SSDFS_ERR("ACL: "
+			  "memory allocator suffers from %lld leaks\n",
+			  atomic64_read(&ssdfs_acl_memory_leaks));
+	}
+
+	if (atomic64_read(&ssdfs_acl_cache_leaks) != 0) {
+		SSDFS_ERR("ACL: "
+			  "caches suffers from %lld leaks\n",
+			  atomic64_read(&ssdfs_acl_cache_leaks));
+	}
+#endif /* CONFIG_SSDFS_MEMORY_LEAKS_ACCOUNTING */
+}
+
+struct posix_acl *ssdfs_get_acl(struct inode *inode, int type, bool rcu)
+{
+	struct posix_acl *acl;
+	char *xattr_name;
+	int name_index;
+	char *value = NULL;
+	ssize_t size;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, type %#x\n",
+		  (unsigned long)inode->i_ino, type);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (rcu)
+		return ERR_PTR(-ECHILD);
+
+	switch (type) {
+	case ACL_TYPE_ACCESS:
+		name_index = SSDFS_POSIX_ACL_ACCESS_XATTR_ID;
+		xattr_name = XATTR_NAME_POSIX_ACL_ACCESS;
+		break;
+	case ACL_TYPE_DEFAULT:
+		name_index = SSDFS_POSIX_ACL_DEFAULT_XATTR_ID;
+		xattr_name = XATTR_NAME_POSIX_ACL_DEFAULT;
+		break;
+	default:
+		SSDFS_ERR("unknown type %#x\n", type);
+		return ERR_PTR(-EINVAL);
+	}
+
+	size = __ssdfs_getxattr(inode, name_index, xattr_name, NULL, 0);
+
+	if (size > 0) {
+		value = ssdfs_acl_kzalloc(size, GFP_KERNEL);
+		if (unlikely(!value)) {
+			SSDFS_ERR("unable to allocate memory\n");
+			return ERR_PTR(-ENOMEM);
+		}
+		size = __ssdfs_getxattr(inode, name_index, xattr_name,
+					value, size);
+	}
+
+	if (size > 0)
+		acl = posix_acl_from_xattr(&init_user_ns, value, size);
+	else if (size == -ENODATA)
+		acl = NULL;
+	else
+		acl = ERR_PTR(size);
+
+	ssdfs_acl_kfree(value);
+	return acl;
+}
+
+static
+int __ssdfs_set_acl(struct inode *inode, struct posix_acl *acl, int type)
+{
+	int name_index;
+	char *xattr_name;
+	size_t size = 0;
+	char *value = NULL;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, type %#x, acl %p\n",
+		  (unsigned long)inode->i_ino, type, acl);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	switch (type) {
+	case ACL_TYPE_ACCESS:
+		name_index = SSDFS_POSIX_ACL_ACCESS_XATTR_ID;
+		xattr_name = XATTR_NAME_POSIX_ACL_ACCESS;
+		break;
+
+	case ACL_TYPE_DEFAULT:
+		name_index = SSDFS_POSIX_ACL_DEFAULT_XATTR_ID;
+		xattr_name = XATTR_NAME_POSIX_ACL_DEFAULT;
+		if (!S_ISDIR(inode->i_mode))
+			return acl ? -EACCES : 0;
+		break;
+
+	default:
+		SSDFS_ERR("unknown type %#x\n", type);
+		return -EINVAL;
+	}
+
+	if (acl) {
+		size = posix_acl_xattr_size(acl->a_count);
+		value = ssdfs_acl_kzalloc(size, GFP_KERNEL);
+		if (!value) {
+			SSDFS_ERR("unable to allocate memory\n");
+			return -ENOMEM;
+		}
+		err = posix_acl_to_xattr(&init_user_ns, acl, value, size);
+		if (err < 0) {
+			SSDFS_ERR("unable to convert acl to xattr\n");
+			goto end_set_acl;
+		}
+	}
+
+	err = __ssdfs_setxattr(inode, name_index, xattr_name, value, size, 0);
+
+end_set_acl:
+	ssdfs_acl_kfree(value);
+
+	if (!err)
+		set_cached_acl(inode, type, acl);
+
+	return err;
+}
+
+int ssdfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
+		  struct posix_acl *acl, int type)
+{
+	int update_mode = 0;
+	struct inode *inode = d_inode(dentry);
+	umode_t mode = inode->i_mode;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, type %#x, acl %p\n",
+		  (unsigned long)inode->i_ino, type, acl);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (type == ACL_TYPE_ACCESS && acl) {
+		err = posix_acl_update_mode(idmap, inode, &mode, &acl);
+		if (err)
+			goto end_set_acl;
+
+		if (mode != inode->i_mode)
+			update_mode = 1;
+	}
+
+	err = __ssdfs_set_acl(inode, acl, type);
+	if (!err && update_mode) {
+		inode->i_mode = mode;
+
+		inode_set_ctime_to_ts(inode, current_time(inode));
+		mark_inode_dirty(inode);
+	}
+
+end_set_acl:
+	return err;
+}
+
+int ssdfs_init_acl(struct inode *inode, struct inode *dir)
+{
+	struct posix_acl *default_acl, *acl;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("dir_ino %lu, ino %lu\n",
+		  (unsigned long)dir->i_ino, (unsigned long)inode->i_ino);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	err = posix_acl_create(dir, &inode->i_mode, &default_acl, &acl);
+	if (err)
+		return err;
+
+	if (default_acl) {
+		err = __ssdfs_set_acl(inode, default_acl, ACL_TYPE_DEFAULT);
+		posix_acl_release(default_acl);
+	}
+
+	if (acl) {
+		if (!err)
+			err = __ssdfs_set_acl(inode, acl, ACL_TYPE_ACCESS);
+		posix_acl_release(acl);
+	}
+	return err;
+}
diff --git a/fs/ssdfs/acl.h b/fs/ssdfs/acl.h
new file mode 100644
index 000000000000..8e3f3b5131ae
--- /dev/null
+++ b/fs/ssdfs/acl.h
@@ -0,0 +1,54 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ *
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/acl.h - ACLs support declarations.
+ *
+ * Copyright (c) 2014-2019 HGST, a Western Digital Company.
+ *              http://www.hgst.com/
+ * Copyright (c) 2014-2026 Viacheslav Dubeyko <slava@dubeyko.com>
+ *              http://www.ssdfs.org/
+ *
+ * (C) Copyright 2014-2019, HGST, Inc., All rights reserved.
+ *
+ * Created by HGST, San Jose Research Center, Storage Architecture Group
+ *
+ * Authors: Viacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Acknowledgement: Cyril Guyot
+ *                  Zvonimir Bandic
+ */
+
+#ifndef _SSDFS_ACL_H
+#define _SSDFS_ACL_H
+
+#include <linux/posix_acl_xattr.h>
+
+#ifdef CONFIG_SSDFS_POSIX_ACL
+
+#define set_posix_acl_flag(sb) \
+	((sb)->s_flags |= SB_POSIXACL)
+
+/* acl.c */
+struct posix_acl *ssdfs_get_acl(struct inode *, int, bool);
+int ssdfs_set_acl(struct mnt_idmap *idmap, struct dentry *,
+		  struct posix_acl *, int);
+int ssdfs_init_acl(struct inode *, struct inode *);
+
+#else  /* CONFIG_SSDFS_POSIX_ACL */
+
+#define set_posix_acl_flag(sb) \
+	((sb)->s_flags &= ~SB_POSIXACL)
+
+#define ssdfs_get_acl NULL
+#define ssdfs_set_acl NULL
+
+static inline int ssdfs_init_acl(struct inode *inode, struct inode *dir)
+{
+	return 0;
+}
+
+#endif  /* CONFIG_SSDFS_POSIX_ACL */
+
+#endif /* _SSDFS_ACL_H */
diff --git a/fs/ssdfs/xattr.c b/fs/ssdfs/xattr.c
new file mode 100644
index 000000000000..3e2b38bc2985
--- /dev/null
+++ b/fs/ssdfs/xattr.c
@@ -0,0 +1,1700 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ *
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/xattr.c - extended attributes support implementation.
+ *
+ * Copyright (c) 2014-2019 HGST, a Western Digital Company.
+ *              http://www.hgst.com/
+ * Copyright (c) 2014-2026 Viacheslav Dubeyko <slava@dubeyko.com>
+ *              http://www.ssdfs.org/
+ *
+ * (C) Copyright 2014-2019, HGST, Inc., All rights reserved.
+ *
+ * Created by HGST, San Jose Research Center, Storage Architecture Group
+ *
+ * Authors: Viacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Acknowledgement: Cyril Guyot
+ *                  Zvonimir Bandic
+ */
+
+#include <linux/kernel.h>
+#include <linux/rwsem.h>
+#include <linux/pagevec.h>
+#include <linux/sched/signal.h>
+
+#include "peb_mapping_queue.h"
+#include "peb_mapping_table_cache.h"
+#include "folio_vector.h"
+#include "ssdfs.h"
+#include "folio_array.h"
+#include "peb.h"
+#include "offset_translation_table.h"
+#include "peb_container.h"
+#include "segment_bitmap.h"
+#include "segment.h"
+#include "btree_search.h"
+#include "btree_node.h"
+#include "btree.h"
+#include "xattr_tree.h"
+#include "shared_dictionary.h"
+#include "dentries_tree.h"
+#include "xattr.h"
+
+const struct xattr_handler *ssdfs_xattr_handlers[] = {
+	&ssdfs_xattr_user_handler,
+	&ssdfs_xattr_trusted_handler,
+#ifdef CONFIG_SSDFS_SECURITY
+	&ssdfs_xattr_security_handler,
+#endif
+	NULL
+};
+
+static
+int ssdfs_xattrs_tree_get_start_hash(struct ssdfs_xattrs_btree_info *tree,
+				     u64 *start_hash)
+{
+	struct ssdfs_btree_index *index;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!tree || !start_hash);
+
+	SSDFS_DBG("tree %p, start_hash %p\n",
+		  tree, start_hash);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	*start_hash = U64_MAX;
+
+	switch (atomic_read(&tree->state)) {
+	case SSDFS_XATTR_BTREE_CREATED:
+	case SSDFS_XATTR_BTREE_INITIALIZED:
+	case SSDFS_XATTR_BTREE_DIRTY:
+		/* expected state */
+		break;
+
+	default:
+		SSDFS_ERR("invalid xattrs tree's state %#x\n",
+			  atomic_read(&tree->state));
+		return -ERANGE;
+	};
+
+	down_read(&tree->lock);
+
+	if (!tree->root) {
+		err = -ERANGE;
+		SSDFS_ERR("root node pointer is NULL\n");
+		goto finish_get_start_hash;
+	}
+
+	index = &tree->root->indexes[SSDFS_ROOT_NODE_LEFT_LEAF_NODE];
+	*start_hash = le64_to_cpu(index->hash);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("start_hash %llx\n", *start_hash);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+finish_get_start_hash:
+	up_read(&tree->lock);
+
+	return err;
+}
+
+static
+int ssdfs_xattrs_tree_get_next_hash(struct ssdfs_xattrs_btree_info *tree,
+				    struct ssdfs_btree_search *search,
+				    u64 *next_hash)
+{
+	u64 old_hash;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!tree || !search || !next_hash);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	old_hash = le64_to_cpu(search->node.found_index.index.hash);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("search %p, next_hash %p, old (node %u, hash %llx)\n",
+		  search, next_hash, search->node.id, old_hash);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	switch (atomic_read(&tree->type)) {
+	case SSDFS_INLINE_XATTR:
+	case SSDFS_INLINE_XATTR_ARRAY:
+		SSDFS_DBG("inline xattrs array is unsupported\n");
+		return -ENOENT;
+
+	case SSDFS_PRIVATE_XATTR_BTREE:
+		/* expected tree type */
+		break;
+
+	default:
+		SSDFS_ERR("invalid tree type %#x\n",
+			  atomic_read(&tree->type));
+		return -ERANGE;
+	}
+
+	down_read(&tree->lock);
+	err = ssdfs_btree_get_next_hash(tree->generic_tree, search, next_hash);
+	up_read(&tree->lock);
+
+	return err;
+}
+
+static
+int ssdfs_xattrs_tree_node_hash_range(struct ssdfs_xattrs_btree_info *tree,
+					struct ssdfs_btree_search *search,
+					u64 *start_hash, u64 *end_hash,
+					u16 *items_count)
+{
+	struct ssdfs_xattr_entry *cur_xattr;
+	u16 inline_count;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!search || !start_hash || !end_hash || !items_count);
+
+	SSDFS_DBG("search %p, start_hash %p, "
+		  "end_hash %p, items_count %p\n",
+		  tree, start_hash, end_hash, items_count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	*start_hash = *end_hash = U64_MAX;
+	*items_count = 0;
+
+	switch (atomic_read(&tree->state)) {
+	case SSDFS_XATTR_BTREE_CREATED:
+	case SSDFS_XATTR_BTREE_INITIALIZED:
+	case SSDFS_XATTR_BTREE_DIRTY:
+		/* expected state */
+		break;
+
+	default:
+		SSDFS_ERR("invalid xattrs tree's state %#x\n",
+			  atomic_read(&tree->state));
+		return -ERANGE;
+	};
+
+	switch (atomic_read(&tree->type)) {
+	case SSDFS_INLINE_XATTR:
+	case SSDFS_INLINE_XATTR_ARRAY:
+		down_read(&tree->lock);
+
+		if (!tree->inline_xattrs) {
+			err = -ERANGE;
+			SSDFS_ERR("inline tree's pointer is empty\n");
+			goto finish_process_inline_tree;
+		}
+
+		inline_count = tree->inline_count;
+
+		if (inline_count >= U16_MAX) {
+			err = -ERANGE;
+			SSDFS_ERR("unexpected xattrs count %u\n",
+				  inline_count);
+			goto finish_process_inline_tree;
+		}
+
+		*items_count = inline_count;
+
+		if (*items_count == 0)
+			goto finish_process_inline_tree;
+
+		cur_xattr = &tree->inline_xattrs[0];
+		*start_hash = le64_to_cpu(cur_xattr->name_hash);
+
+		if (inline_count > tree->inline_capacity) {
+			err = -ERANGE;
+			SSDFS_ERR("xattrs_count %u > max_value %u\n",
+				  inline_count,
+				  tree->inline_capacity);
+			goto finish_process_inline_tree;
+		}
+
+		cur_xattr = &tree->inline_xattrs[inline_count - 1];
+		*end_hash = le64_to_cpu(cur_xattr->name_hash);
+
+finish_process_inline_tree:
+		up_read(&tree->lock);
+		break;
+
+	case SSDFS_PRIVATE_XATTR_BTREE:
+		err = ssdfs_btree_node_get_hash_range(search,
+						      start_hash,
+						      end_hash,
+						      items_count);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to get hash range: err %d\n",
+				  err);
+			goto finish_extract_hash_range;
+		}
+		break;
+
+	default:
+		SSDFS_ERR("invalid tree type %#x\n",
+			  atomic_read(&tree->type));
+		return -ERANGE;
+	}
+
+finish_extract_hash_range:
+	return err;
+}
+
+static
+int ssdfs_xattrs_tree_check_search_result(struct ssdfs_btree_search *search)
+{
+	size_t xattr_size = sizeof(struct ssdfs_xattr_entry);
+	u16 items_count;
+	size_t buf_size;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!search);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	switch (search->result.state) {
+	case SSDFS_BTREE_SEARCH_VALID_ITEM:
+		/* expected state */
+		break;
+
+	default:
+		SSDFS_ERR("unexpected result's state %#x\n",
+			  search->result.state);
+		return  -ERANGE;
+	}
+
+	switch (search->result.raw_buf.state) {
+	case SSDFS_BTREE_SEARCH_INLINE_BUFFER:
+	case SSDFS_BTREE_SEARCH_EXTERNAL_BUFFER:
+		if (!search->result.raw_buf.place.ptr) {
+			SSDFS_ERR("buffer pointer is NULL\n");
+			return -ERANGE;
+		}
+		break;
+
+	default:
+		SSDFS_ERR("unexpected buffer's state\n");
+		return -ERANGE;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(search->result.raw_buf.items_count >= U16_MAX);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	items_count = (u16)search->result.raw_buf.items_count;
+
+	if (items_count == 0) {
+		SSDFS_ERR("items_in_buffer %u\n",
+			  items_count);
+		return -ENOENT;
+	} else if (items_count != search->result.count) {
+		SSDFS_ERR("items_count %u != search->result.count %u\n",
+			  items_count, search->result.count);
+		return -ERANGE;
+	}
+
+	buf_size = xattr_size * items_count;
+
+	if (buf_size != search->result.raw_buf.size) {
+		SSDFS_ERR("buf_size %zu != search->result.raw_buf.size %zu\n",
+			  buf_size,
+			  search->result.raw_buf.size);
+		return -ERANGE;
+	}
+
+	return 0;
+}
+
+static
+bool is_invalid_xattr(struct ssdfs_xattr_entry *xattr)
+{
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!xattr);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (le64_to_cpu(xattr->name_hash) >= U64_MAX) {
+		SSDFS_ERR("invalid hash_code\n");
+		return true;
+	}
+
+	if (xattr->name_len > SSDFS_MAX_NAME_LEN) {
+		SSDFS_ERR("invalid name_len %u\n",
+			  xattr->name_len);
+		return true;
+	}
+
+	if (xattr->name_type <= SSDFS_XATTR_NAME_UNKNOWN_TYPE ||
+	    xattr->name_type >= SSDFS_XATTR_NAME_TYPE_MAX) {
+		SSDFS_ERR("invalid name_type %#x\n",
+			  xattr->name_type);
+		return true;
+	}
+
+	if (xattr->name_flags & ~SSDFS_XATTR_NAME_FLAGS_MASK) {
+		SSDFS_ERR("invalid set of flags %#x\n",
+			  xattr->name_flags);
+		return true;
+	}
+
+	if (xattr->blob_type <= SSDFS_XATTR_BLOB_UNKNOWN_TYPE ||
+	    xattr->blob_type >= SSDFS_XATTR_BLOB_TYPE_MAX) {
+		SSDFS_ERR("invalid blob_type %#x\n",
+			  xattr->blob_type);
+		return true;
+	}
+
+	if (xattr->blob_flags & ~SSDFS_XATTR_BLOB_FLAGS_MASK) {
+		SSDFS_ERR("invalid set of flags %#x\n",
+			  xattr->blob_flags);
+		return true;
+	}
+
+	return false;
+}
+
+static
+ssize_t ssdfs_copy_name2buffer(struct ssdfs_shared_dict_btree_info *dict,
+				struct ssdfs_xattr_entry *xattr,
+				struct ssdfs_btree_search *search,
+				ssize_t offset,
+				char *buffer, size_t size)
+{
+	u64 hash;
+	size_t prefix_len, name_len;
+	ssize_t copied = 0;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!xattr || !buffer);
+
+	SSDFS_DBG("xattr %p, offset %zd, "
+		  "buffer %p, size %zu\n",
+		  xattr, offset, buffer, size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	hash = le64_to_cpu(xattr->name_hash);
+
+	if ((copied + xattr->name_len) >= size) {
+		SSDFS_ERR("copied %zd, name_len %u, size %zu\n",
+			  copied, xattr->name_len, size);
+		return -ERANGE;
+	}
+
+	if (xattr->name_flags & SSDFS_XATTR_HAS_EXTERNAL_STRING) {
+		err = ssdfs_shared_dict_get_name(dict, hash,
+						 &search->name.string);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to extract the name: "
+				  "hash %llx, err %d\n",
+				  hash, err);
+			return err;
+		}
+
+		switch (xattr->name_type) {
+		case SSDFS_XATTR_REGULAR_NAME:
+			/* do nothing here */
+			break;
+
+		case SSDFS_XATTR_USER_REGULAR_NAME:
+			prefix_len =
+				strlen(SSDFS_NS_PREFIX[SSDFS_USER_NS_INDEX]);
+			err = ssdfs_memcpy(buffer, offset, size,
+				     SSDFS_NS_PREFIX[SSDFS_USER_NS_INDEX],
+				     0, prefix_len,
+				     prefix_len);
+			BUG_ON(unlikely(err != 0));
+			offset += prefix_len;
+			copied += prefix_len;
+			break;
+
+		case SSDFS_XATTR_TRUSTED_REGULAR_NAME:
+			prefix_len =
+				strlen(SSDFS_NS_PREFIX[SSDFS_TRUSTED_NS_INDEX]);
+			err = ssdfs_memcpy(buffer, offset, size,
+				     SSDFS_NS_PREFIX[SSDFS_TRUSTED_NS_INDEX],
+				     0, prefix_len,
+				     prefix_len);
+			BUG_ON(unlikely(err != 0));
+			offset += prefix_len;
+			copied += prefix_len;
+			break;
+
+		case SSDFS_XATTR_SYSTEM_REGULAR_NAME:
+			prefix_len =
+				strlen(SSDFS_NS_PREFIX[SSDFS_SYSTEM_NS_INDEX]);
+			err = ssdfs_memcpy(buffer, offset, size,
+				     SSDFS_NS_PREFIX[SSDFS_SYSTEM_NS_INDEX],
+				     0, prefix_len,
+				     prefix_len);
+			BUG_ON(unlikely(err != 0));
+			offset += prefix_len;
+			copied += prefix_len;
+			break;
+
+		case SSDFS_XATTR_SECURITY_REGULAR_NAME:
+			prefix_len =
+			    strlen(SSDFS_NS_PREFIX[SSDFS_SECURITY_NS_INDEX]);
+			err = ssdfs_memcpy(buffer, offset, size,
+				     SSDFS_NS_PREFIX[SSDFS_SECURITY_NS_INDEX],
+				     0, prefix_len,
+				     prefix_len);
+			BUG_ON(unlikely(err != 0));
+			offset += prefix_len;
+			copied += prefix_len;
+			break;
+
+		default:
+			SSDFS_ERR("unexpected name type %#x\n",
+				  xattr->name_type);
+			return -EIO;
+		}
+
+		err = ssdfs_memcpy(buffer, offset, size,
+				   search->name.string.str,
+				   0, SSDFS_MAX_NAME_LEN,
+				   search->name.string.len);
+		BUG_ON(unlikely(err != 0));
+
+		offset += search->name.string.len;
+		copied += search->name.string.len;
+
+		if (offset >= size) {
+			SSDFS_ERR("invalid offset: "
+				  "offset %zd, size %zu\n",
+				  offset, size);
+			return -ERANGE;
+		}
+
+		memset(buffer + offset, 0, size - offset);
+	} else {
+		switch (xattr->name_type) {
+		case SSDFS_XATTR_INLINE_NAME:
+			/* do nothing here */
+			break;
+
+		case SSDFS_XATTR_USER_INLINE_NAME:
+			prefix_len =
+				strlen(SSDFS_NS_PREFIX[SSDFS_USER_NS_INDEX]);
+			err = ssdfs_memcpy(buffer, offset, size,
+				     SSDFS_NS_PREFIX[SSDFS_USER_NS_INDEX],
+				     0, prefix_len,
+				     prefix_len);
+			BUG_ON(unlikely(err != 0));
+			offset += prefix_len;
+			copied += prefix_len;
+			break;
+
+		case SSDFS_XATTR_TRUSTED_INLINE_NAME:
+			prefix_len =
+				strlen(SSDFS_NS_PREFIX[SSDFS_TRUSTED_NS_INDEX]);
+			err = ssdfs_memcpy(buffer, offset, size,
+				     SSDFS_NS_PREFIX[SSDFS_TRUSTED_NS_INDEX],
+				     0, prefix_len,
+				     prefix_len);
+			BUG_ON(unlikely(err != 0));
+			offset += prefix_len;
+			copied += prefix_len;
+			break;
+
+		case SSDFS_XATTR_SYSTEM_INLINE_NAME:
+			prefix_len =
+				strlen(SSDFS_NS_PREFIX[SSDFS_SYSTEM_NS_INDEX]);
+			err = ssdfs_memcpy(buffer, offset, size,
+				     SSDFS_NS_PREFIX[SSDFS_SYSTEM_NS_INDEX],
+				     0, prefix_len,
+				     prefix_len);
+			BUG_ON(unlikely(err != 0));
+			offset += prefix_len;
+			copied += prefix_len;
+			break;
+
+		case SSDFS_XATTR_SECURITY_INLINE_NAME:
+			prefix_len =
+			    strlen(SSDFS_NS_PREFIX[SSDFS_SECURITY_NS_INDEX]);
+			err = ssdfs_memcpy(buffer, offset, size,
+				     SSDFS_NS_PREFIX[SSDFS_SECURITY_NS_INDEX],
+				     0, prefix_len,
+				     prefix_len);
+			offset += prefix_len;
+			copied += prefix_len;
+			break;
+
+		default:
+			SSDFS_ERR("unexpected name type %#x\n",
+				  xattr->name_type);
+			return -EIO;
+		}
+
+		name_len = xattr->name_len;
+
+		err = ssdfs_memcpy(buffer, offset, size,
+				   xattr->inline_string,
+				   0, SSDFS_XATTR_INLINE_NAME_MAX_LEN,
+				   name_len);
+		BUG_ON(unlikely(err != 0));
+
+		offset += name_len;
+		copied += name_len;
+
+		if (offset >= size) {
+			SSDFS_ERR("invalid offset: "
+				  "offset %zd, size %zu\n",
+				  offset, size);
+			return -ERANGE;
+		}
+
+		memset(buffer + offset, 0, size - offset);
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("XATTR NAME DUMP\n");
+	print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+			     buffer, size);
+	SSDFS_DBG("\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	return copied;
+}
+
+static inline
+size_t ssdfs_calculate_name_length(struct ssdfs_xattr_entry *xattr)
+{
+	size_t prefix_len = 0;
+	size_t name_len = 0;
+
+	switch (xattr->name_type) {
+	case SSDFS_XATTR_INLINE_NAME:
+	case SSDFS_XATTR_REGULAR_NAME:
+		/* do nothing here */
+		break;
+
+	case SSDFS_XATTR_USER_INLINE_NAME:
+	case SSDFS_XATTR_USER_REGULAR_NAME:
+		prefix_len = strlen(SSDFS_NS_PREFIX[SSDFS_USER_NS_INDEX]);
+		break;
+
+	case SSDFS_XATTR_TRUSTED_INLINE_NAME:
+	case SSDFS_XATTR_TRUSTED_REGULAR_NAME:
+		prefix_len = strlen(SSDFS_NS_PREFIX[SSDFS_TRUSTED_NS_INDEX]);
+		break;
+
+	case SSDFS_XATTR_SYSTEM_INLINE_NAME:
+	case SSDFS_XATTR_SYSTEM_REGULAR_NAME:
+		prefix_len = strlen(SSDFS_NS_PREFIX[SSDFS_SYSTEM_NS_INDEX]);
+		break;
+
+	case SSDFS_XATTR_SECURITY_INLINE_NAME:
+	case SSDFS_XATTR_SECURITY_REGULAR_NAME:
+		prefix_len = strlen(SSDFS_NS_PREFIX[SSDFS_SECURITY_NS_INDEX]);
+		break;
+
+	default:
+		/* do nothing */
+		break;
+	}
+
+	name_len = prefix_len + xattr->name_len;
+
+	return name_len;
+}
+
+inline
+ssize_t ssdfs_listxattr_inline_tree(struct inode *inode,
+				    struct ssdfs_btree_search *search,
+				    char *buffer, size_t size)
+{
+	struct ssdfs_inode_info *ii = SSDFS_I(inode);
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct ssdfs_shared_dict_btree_info *dict;
+	struct ssdfs_xattr_entry *xattr;
+	u8 *kaddr;
+	size_t xattr_size = sizeof(struct ssdfs_xattr_entry);
+	u16 items_count;
+	ssize_t res, copied = 0;
+	int i;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, buffer %p, size %zu\n",
+		  inode->i_ino, buffer, size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	dict = fsi->shdictree;
+	if (!dict) {
+		SSDFS_ERR("shared dictionary is absent\n");
+		return -ERANGE;
+	}
+
+	down_read(&ii->lock);
+
+	if (!ii->xattrs_tree) {
+		err = -ERANGE;
+		SSDFS_ERR("unexpected xattrs tree absence\n");
+		goto finish_tree_processing;
+	}
+
+	err = ssdfs_xattrs_tree_extract_range(ii->xattrs_tree,
+					      0,
+					      SSDFS_DEFAULT_INLINE_XATTR_COUNT,
+					      search);
+	if (err == -ENOENT) {
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("unable to extract inline xattr: "
+			  "ino %lu\n",
+			  inode->i_ino);
+#endif /* CONFIG_SSDFS_DEBUG */
+		goto finish_tree_processing;
+	} else if (unlikely(err)) {
+		SSDFS_ERR("fail to extract inline xattr: "
+			  "ino %lu, err %d\n",
+			  inode->i_ino, err);
+		goto finish_tree_processing;
+	}
+
+finish_tree_processing:
+	up_read(&ii->lock);
+
+	if (err == -ENOENT) {
+		err = 0;
+		goto clean_up;
+	} else if (unlikely(err))
+		goto clean_up;
+
+	err = ssdfs_xattrs_tree_check_search_result(search);
+	if (unlikely(err)) {
+		SSDFS_ERR("corrupted search result: "
+			  "err %d\n", err);
+		goto clean_up;
+	}
+
+	items_count = search->result.count;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!search->result.raw_buf.place.ptr);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	for (i = 0; i < items_count; i++) {
+		kaddr = (u8 *)search->result.raw_buf.place.ptr;
+		xattr = (struct ssdfs_xattr_entry *)(kaddr + (i * xattr_size));
+
+		if (is_invalid_xattr(xattr)) {
+			err = -EIO;
+			SSDFS_ERR("found corrupted xattr\n");
+			goto clean_up;
+		}
+
+		if (buffer) {
+			res = ssdfs_copy_name2buffer(dict, xattr,
+						     search, copied,
+						     buffer, size);
+			if (res < 0) {
+				err = res;
+				SSDFS_ERR("failed to copy name: "
+					  "err %d\n", err);
+				goto clean_up;
+			} else
+				copied += res + 1;
+		} else
+			copied += ssdfs_calculate_name_length(xattr) + 1;
+	}
+
+clean_up:
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("copied %zd\n", copied);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	return err < 0 ? err : copied;
+}
+
+inline
+ssize_t ssdfs_listxattr_generic_tree(struct inode *inode,
+				     struct ssdfs_btree_search *search,
+				     char *buffer, size_t size)
+{
+	struct ssdfs_inode_info *ii = SSDFS_I(inode);
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct ssdfs_shared_dict_btree_info *dict;
+	struct ssdfs_xattr_entry *xattr;
+	u8 *kaddr;
+	size_t xattr_size = sizeof(struct ssdfs_xattr_entry);
+	u64 start_hash, end_hash;
+	u16 items_count;
+	ssize_t res, copied = 0;
+	int i;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, buffer %p, size %zu\n",
+		  inode->i_ino, buffer, size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	dict = fsi->shdictree;
+	if (!dict) {
+		SSDFS_ERR("shared dictionary is absent\n");
+		return -ERANGE;
+	}
+
+	down_read(&ii->lock);
+
+	if (!ii->xattrs_tree) {
+		err = -ERANGE;
+		SSDFS_ERR("unexpected xattrs tree absence\n");
+		goto finish_get_start_hash;
+	}
+
+	err = ssdfs_xattrs_tree_get_start_hash(ii->xattrs_tree,
+						&start_hash);
+	if (err == -ENOENT)
+		goto finish_get_start_hash;
+	else if (unlikely(err)) {
+		SSDFS_ERR("fail to get start root hash: err %d\n", err);
+		goto finish_get_start_hash;
+	} else if (start_hash >= U64_MAX) {
+		err = -ERANGE;
+		SSDFS_ERR("invalid hash value\n");
+		goto finish_get_start_hash;
+	}
+
+finish_get_start_hash:
+	up_read(&ii->lock);
+
+	if (err == -ENOENT) {
+		err = 0;
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("unable to extract start hash: "
+			  "ino %lu\n",
+			  inode->i_ino);
+#endif /* CONFIG_SSDFS_DEBUG */
+		goto clean_up;
+	} else if (unlikely(err))
+		goto clean_up;
+
+	do {
+		ssdfs_btree_search_init(search);
+
+		/* allow ssdfs_listxattr_generic_tree() to be interrupted */
+		if (fatal_signal_pending(current)) {
+			err = -ERESTARTSYS;
+			goto clean_up;
+		}
+		cond_resched();
+
+		down_read(&ii->lock);
+
+		err = ssdfs_xattrs_tree_find_leaf_node(ii->xattrs_tree,
+							start_hash,
+							search);
+		if (err == -ENODATA) {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("unable to find a leaf node: "
+				  "hash %llx, err %d\n",
+				  start_hash, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+			goto finish_tree_processing;
+		} else if (unlikely(err)) {
+			SSDFS_ERR("fail to find a leaf node: "
+				  "hash %llx, err %d\n",
+				  start_hash, err);
+			goto finish_tree_processing;
+		}
+
+		err = ssdfs_xattrs_tree_node_hash_range(ii->xattrs_tree,
+							search,
+							&start_hash,
+							&end_hash,
+							&items_count);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to get node's hash range: "
+				  "err %d\n", err);
+			goto finish_tree_processing;
+		}
+
+		if (items_count == 0) {
+			err = -ENOENT;
+			SSDFS_DBG("empty leaf node\n");
+			goto finish_tree_processing;
+		}
+
+		if (start_hash > end_hash) {
+			err = -ENOENT;
+			goto finish_tree_processing;
+		}
+
+		err = ssdfs_xattrs_tree_extract_range(ii->xattrs_tree,
+							0, items_count,
+							search);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to extract the range: "
+				  "items_count %u, err %d\n",
+				  items_count, err);
+			goto finish_tree_processing;
+		}
+
+finish_tree_processing:
+		up_read(&ii->lock);
+
+		if (err == -ENOENT) {
+			err = 0;
+			goto clean_up;
+		} else if (unlikely(err))
+			goto clean_up;
+
+		err = ssdfs_xattrs_tree_check_search_result(search);
+		if (unlikely(err)) {
+			SSDFS_ERR("corrupted search result: "
+				  "err %d\n", err);
+			goto clean_up;
+		}
+
+		items_count = search->result.count;
+
+		for (i = 0; i < items_count; i++) {
+			u64 hash;
+
+			kaddr = (u8 *)search->result.raw_buf.place.ptr;
+			xattr =
+			    (struct ssdfs_xattr_entry *)(kaddr +
+							    (i * xattr_size));
+			hash = le64_to_cpu(xattr->name_hash);
+
+			if (is_invalid_xattr(xattr)) {
+				err = -EIO;
+				SSDFS_ERR("found corrupted xattr\n");
+				goto clean_up;
+			}
+
+			if (buffer) {
+				res = ssdfs_copy_name2buffer(dict, xattr,
+							     search, copied,
+							     buffer, size);
+				if (res < 0) {
+					err = res;
+					SSDFS_ERR("failed to copy name: "
+						  "err %d\n", err);
+					goto clean_up;
+				} else
+					copied += res + 1;
+			} else {
+				copied +=
+					ssdfs_calculate_name_length(xattr) + 1;
+			}
+
+			start_hash = hash;
+		}
+
+		if (start_hash != end_hash) {
+			err = -ERANGE;
+			SSDFS_ERR("cur_hash %llx != end_hash %llx\n",
+				  start_hash, end_hash);
+			goto clean_up;
+		}
+
+		start_hash = end_hash + 1;
+
+		down_read(&ii->lock);
+		err = ssdfs_xattrs_tree_get_next_hash(ii->xattrs_tree,
+						      search,
+						      &start_hash);
+		up_read(&ii->lock);
+
+		ssdfs_btree_search_forget_parent_node(search);
+		ssdfs_btree_search_forget_child_node(search);
+
+		if (err == -ENOENT) {
+			err = 0;
+			SSDFS_DBG("no more xattrs in the tree\n");
+			goto clean_up;
+		} else if (unlikely(err)) {
+			SSDFS_ERR("fail to get next hash: err %d\n",
+				  err);
+			goto clean_up;
+		}
+	} while (start_hash < U64_MAX);
+
+clean_up:
+	return err < 0 ? err : copied;
+}
+
+/*
+ * Copy a list of attribute names into the buffer
+ * provided, or compute the buffer size required.
+ * Buffer is NULL to compute the size of the buffer required.
+ *
+ * Returns a negative error number on failure, or the number of bytes
+ * used / required on success.
+ */
+ssize_t ssdfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
+{
+	struct inode *inode = d_inode(dentry);
+	struct ssdfs_inode_info *ii = SSDFS_I(inode);
+	struct ssdfs_btree_search *search;
+	int private_flags;
+	ssize_t copied = 0;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, buffer %p, size %zu\n",
+		  inode->i_ino, buffer, size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	private_flags = atomic_read(&ii->private_flags);
+
+	switch (private_flags) {
+	case SSDFS_INODE_HAS_INLINE_XATTR:
+	case SSDFS_INODE_HAS_XATTR_BTREE:
+		/* xattrs tree exists */
+		break;
+
+	default:
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("xattrs tree is absent: "
+			  "ino %lu\n",
+			  inode->i_ino);
+#endif /* CONFIG_SSDFS_DEBUG */
+		return 0;
+	}
+
+	search = ssdfs_btree_search_alloc();
+	if (!search) {
+		SSDFS_ERR("fail to allocate btree search object\n");
+		return -ENOMEM;
+	}
+
+	if (!ii->xattrs_tree) {
+		err = -ERANGE;
+		SSDFS_ERR("unexpected xattrs tree absence\n");
+		goto clean_up;
+	}
+
+	switch (atomic_read(&ii->xattrs_tree->type)) {
+	case SSDFS_INLINE_XATTR:
+	case SSDFS_INLINE_XATTR_ARRAY:
+		ssdfs_btree_search_init(search);
+		copied = ssdfs_listxattr_inline_tree(inode, search,
+						     buffer, size);
+		if (unlikely(copied < 0)) {
+			err = copied;
+			SSDFS_ERR("fail to extract the inline range: "
+				  "err %d\n", err);
+			goto clean_up;
+		}
+		break;
+
+	case SSDFS_PRIVATE_XATTR_BTREE:
+		copied = ssdfs_listxattr_generic_tree(inode, search,
+						      buffer, size);
+		if (unlikely(copied < 0)) {
+			err = copied;
+			SSDFS_ERR("fail to extract the range: "
+				  "err %d\n", err);
+			goto clean_up;
+		}
+		break;
+
+	default:
+		err = -ERANGE;
+		SSDFS_ERR("invalid xattrs tree type %#x\n",
+			  atomic_read(&ii->xattrs_tree->type));
+		goto clean_up;
+	}
+
+clean_up:
+	ssdfs_btree_search_free(search);
+
+	return err < 0 ? err : copied;
+}
+
+/*
+ * Read external blob
+ */
+static
+int ssdfs_xattr_read_external_blob(struct ssdfs_fs_info *fsi,
+				   struct inode *inode,
+				   struct ssdfs_xattr_entry *xattr,
+				   void *value, size_t size)
+{
+	struct ssdfs_segment_request *req;
+	struct ssdfs_peb_container *pebc;
+	struct ssdfs_blk2off_table *table;
+	struct ssdfs_offset_position pos;
+	struct ssdfs_segment_info *si;
+	struct ssdfs_segment_search_state seg_search;
+	struct ssdfs_request_content_block *block;
+	struct ssdfs_content_block *blk_state;
+	struct folio *folio;
+	u16 blob_size;
+	u64 seg_id;
+	u32 logical_blk;
+	u32 len;
+	u32 batch_size;
+	u64 logical_offset;
+	u32 data_bytes;
+	u32 copied_bytes = 0;
+	struct completion *end;
+	int i, j;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!fsi || !inode || !xattr || !value);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	seg_id = le64_to_cpu(xattr->blob.descriptor.extent.seg_id);
+	logical_blk = le32_to_cpu(xattr->blob.descriptor.extent.logical_blk);
+	len = le32_to_cpu(xattr->blob.descriptor.extent.len);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("seg_id %llu, logical_blk %u, len %u\n",
+		  seg_id, logical_blk, len);
+
+	BUG_ON(seg_id == U64_MAX);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	ssdfs_segment_search_state_init(&seg_search,
+					SSDFS_USER_DATA_SEG_TYPE,
+					seg_id, U64_MAX);
+
+	si = ssdfs_grab_segment(fsi, &seg_search);
+	if (unlikely(IS_ERR_OR_NULL(si))) {
+		err = !si ? -ENOMEM : PTR_ERR(si);
+		if (err == -EINTR) {
+			/*
+			 * Ignore this error.
+			 */
+		} else {
+			SSDFS_ERR("fail to grab segment object: "
+				  "seg %llu, err %d\n",
+				  seg_id, err);
+		}
+		goto fail_get_segment;
+	}
+
+	if (!is_ssdfs_segment_ready_for_requests(si)) {
+		err = ssdfs_wait_segment_init_end(si);
+		if (unlikely(err)) {
+			SSDFS_ERR("segment initialization failed: "
+				  "seg %llu, err %d\n",
+				  si->seg_id, err);
+			goto finish_prepare_request;
+		}
+	}
+
+	blob_size = le16_to_cpu(xattr->blob_len);
+
+	if (blob_size > size) {
+		err = -EINVAL;
+		SSDFS_ERR("invalid request: blob_size %u > size %zu\n",
+			  blob_size, size);
+		goto finish_prepare_request;
+	}
+
+	batch_size = blob_size >> fsi->log_pagesize;
+
+	if (batch_size == 0)
+		batch_size = 1;
+
+	if (batch_size > SSDFS_EXTENT_LEN_MAX) {
+		err = -ERANGE;
+		SSDFS_WARN("invalid memory folios count: "
+			   "blob_size %u, batch_size %u\n",
+			   blob_size, batch_size);
+		goto finish_prepare_request;
+	}
+
+	req = ssdfs_request_alloc();
+	if (IS_ERR_OR_NULL(req)) {
+		err = (req == NULL ? -ENOMEM : PTR_ERR(req));
+		SSDFS_ERR("fail to allocate segment request: err %d\n",
+			  err);
+		goto finish_prepare_request;
+	}
+
+	ssdfs_request_init(req, fsi->pagesize);
+	ssdfs_get_request(req);
+
+	logical_offset = 0;
+	data_bytes = blob_size;
+	ssdfs_request_prepare_logical_extent(inode->i_ino,
+					     (u64)logical_offset,
+					     (u32)data_bytes,
+					     0, 0, req);
+
+	for (i = 0; i < batch_size; i++) {
+		err = ssdfs_request_add_allocated_folio_locked(i, req);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to add folio into request: "
+				  "err %d\n",
+				  err);
+			goto fail_read_blob;
+		}
+	}
+
+	ssdfs_request_define_segment(seg_id, req);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(logical_blk >= U16_MAX);
+	BUG_ON(len >= U16_MAX);
+#endif /* CONFIG_SSDFS_DEBUG */
+	ssdfs_request_define_volume_extent((u16)logical_blk, (u16)len, req);
+
+	ssdfs_request_prepare_internal_data(SSDFS_PEB_READ_REQ,
+					    SSDFS_READ_PAGES_READAHEAD,
+					    SSDFS_REQ_SYNC,
+					    req);
+
+	table = si->blk2off_table;
+
+	err = ssdfs_blk2off_table_get_offset_position(table, logical_blk, &pos);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to convert: "
+			  "seg_id %llu, logical_blk %u, len %u, err %d\n",
+			  seg_id, logical_blk, len, err);
+		goto fail_read_blob;
+	}
+
+	pebc = &si->peb_array[pos.peb_index];
+
+	err = ssdfs_peb_readahead_pages(pebc, req, &end);
+	if (err == -EAGAIN) {
+		err = SSDFS_WAIT_COMPLETION(end);
+		if (unlikely(err)) {
+			SSDFS_ERR("PEB init failed: "
+				  "err %d\n", err);
+			goto fail_read_blob;
+		}
+
+		err = ssdfs_peb_readahead_pages(pebc, req, &end);
+	}
+
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to read page: err %d\n",
+			  err);
+		goto fail_read_blob;
+	}
+
+	for (i = 0; i < req->result.processed_blks; i++)
+		ssdfs_peb_mark_request_block_uptodate(pebc, req, i);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(req->result.content.count == 0);
+
+	for (i = 0; i < req->result.content.count; i++) {
+		block = &req->result.content.blocks[i];
+		blk_state = &block->new_state;
+
+		BUG_ON(folio_batch_count(&blk_state->batch) == 0);
+
+		for (j = 0; j < folio_batch_count(&blk_state->batch); j++) {
+			void *kaddr;
+			u32 processed_bytes = 0;
+			u32 page_index = 0;
+
+			folio = blk_state->batch.folios[j];
+
+			WARN_ON(!folio_test_locked(folio));
+
+			do {
+				kaddr = kmap_local_folio(folio,
+							 processed_bytes);
+				SSDFS_DBG("PAGE DUMP: blk_index %d, "
+					  "folio_index %d, page_index %u\n",
+					  i, j, page_index);
+				print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+						     kaddr,
+						     PAGE_SIZE);
+				SSDFS_DBG("\n");
+				kunmap_local(kaddr);
+
+				processed_bytes += PAGE_SIZE;
+				page_index++;
+			} while (processed_bytes < folio_size(folio));
+		}
+	}
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	for (i = 0; i < req->result.content.count; i++) {
+		block = &req->result.content.blocks[i];
+		blk_state = &block->new_state;
+
+		for (j = 0; j < folio_batch_count(&blk_state->batch); j++) {
+			u32 cur_len;
+
+			folio = blk_state->batch.folios[j];
+
+			if (copied_bytes >= blob_size)
+				goto finish_copy_operation;
+
+			cur_len = min_t(u32, (u32)folio_size(folio),
+					blob_size - copied_bytes);
+
+			err = __ssdfs_memcpy_from_folio(value,
+							copied_bytes, size,
+							folio,
+							0, folio_size(folio),
+							cur_len);
+			if (unlikely(err)) {
+				SSDFS_ERR("fail to copy: "
+					  "copied_bytes %u, cur_len %u\n",
+					  copied_bytes, cur_len);
+				goto fail_read_blob;
+			}
+
+			copied_bytes += cur_len;
+		}
+	}
+
+finish_copy_operation:
+	ssdfs_request_unlock_and_remove_folios(req);
+
+	ssdfs_put_request(req);
+	ssdfs_request_free(req, si);
+
+	ssdfs_segment_put_object(si);
+
+	return 0;
+
+fail_read_blob:
+	ssdfs_request_unlock_and_remove_folios(req);
+	ssdfs_put_request(req);
+	ssdfs_request_free(req, si);
+
+finish_prepare_request:
+	ssdfs_segment_put_object(si);
+
+fail_get_segment:
+	return err;
+}
+
+/*
+ * Copy an extended attribute into the buffer
+ * provided, or compute the buffer size required.
+ * Buffer is NULL to compute the size of the buffer required.
+ *
+ * Returns a negative error number on failure, or the number of bytes
+ * used / required on success.
+ */
+ssize_t __ssdfs_getxattr(struct inode *inode, int name_index, const char *name,
+			 void *value, size_t size)
+{
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct ssdfs_inode_info *ii = SSDFS_I(inode);
+	struct ssdfs_btree_search *search;
+	struct ssdfs_xattr_entry *xattr;
+	u8 *kaddr;
+	size_t name_len;
+	u16 blob_len;
+	u8 blob_type;
+	u8 blob_flags;
+	int private_flags;
+	ssize_t err = 0;
+
+	if (name == NULL) {
+		SSDFS_ERR("name pointer is NULL\n");
+		return -EINVAL;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("name_index %d, name %s, value %p, size %zu\n",
+		  name_index, name, value, size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	name_len = strlen(name);
+	if (name_len > SSDFS_MAX_NAME_LEN)
+		return -ERANGE;
+
+	private_flags = atomic_read(&ii->private_flags);
+
+	switch (private_flags) {
+	case SSDFS_INODE_HAS_INLINE_XATTR:
+	case SSDFS_INODE_HAS_XATTR_BTREE:
+		down_read(&ii->lock);
+
+		if (!ii->xattrs_tree) {
+			err = -ERANGE;
+			SSDFS_WARN("xattrs tree is absent!!!\n");
+			goto finish_search_xattr;
+		}
+
+		search = ssdfs_btree_search_alloc();
+		if (!search) {
+			err = -ENOMEM;
+			SSDFS_ERR("fail to allocate btree search object\n");
+			goto finish_search_xattr;
+		}
+
+		ssdfs_btree_search_init(search);
+
+		err = ssdfs_xattrs_tree_find(ii->xattrs_tree,
+					     name, name_len,
+					     search);
+
+		if (err == -ENODATA) {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("inode %lu hasn't xattr %s\n",
+				  (unsigned long)inode->i_ino,
+				  name);
+#endif /* CONFIG_SSDFS_DEBUG */
+			goto xattr_is_not_available;
+		} else if (unlikely(err)) {
+			SSDFS_ERR("fail to find the xattr: "
+				  "inode %lu, name %s\n",
+				  (unsigned long)inode->i_ino,
+				  name);
+			goto xattr_is_not_available;
+		}
+
+		if (search->result.state != SSDFS_BTREE_SEARCH_VALID_ITEM) {
+			err = -ERANGE;
+			SSDFS_ERR("invalid result's state %#x\n",
+				  search->result.state);
+			goto xattr_is_not_available;
+		}
+
+		switch (search->result.raw_buf.state) {
+		case SSDFS_BTREE_SEARCH_INLINE_BUFFER:
+		case SSDFS_BTREE_SEARCH_EXTERNAL_BUFFER:
+			/* expected state */
+			break;
+
+		default:
+			err = -ERANGE;
+			SSDFS_ERR("invalid buffer state %#x\n",
+				  search->result.raw_buf.state);
+			goto xattr_is_not_available;
+		}
+
+		if (!search->result.raw_buf.place.ptr) {
+			err = -ERANGE;
+			SSDFS_ERR("buffer is absent\n");
+			goto xattr_is_not_available;
+		}
+
+		if (search->result.raw_buf.size == 0) {
+			err = -ERANGE;
+			SSDFS_ERR("result.buf_size %zu\n",
+				  search->result.raw_buf.size);
+			goto xattr_is_not_available;
+		}
+
+		kaddr = (u8 *)search->result.raw_buf.place.ptr;
+		xattr = (struct ssdfs_xattr_entry *)kaddr;
+
+		blob_len = le16_to_cpu(xattr->blob_len);
+		blob_type = xattr->blob_type;
+		blob_flags = xattr->blob_flags;
+
+		switch (blob_type) {
+		case SSDFS_XATTR_INLINE_BLOB:
+			if (blob_len > SSDFS_XATTR_INLINE_BLOB_MAX_LEN) {
+				err = -ERANGE;
+				SSDFS_ERR("invalid blob_len %u\n",
+					  blob_len);
+				goto xattr_is_not_available;
+			}
+			break;
+
+		case SSDFS_XATTR_REGULAR_BLOB:
+			if (!(blob_flags & SSDFS_XATTR_HAS_EXTERNAL_BLOB)) {
+				err = -ERANGE;
+				SSDFS_ERR("invalid set of flags %#x\n",
+					  blob_flags);
+				goto xattr_is_not_available;
+			}
+
+			if (blob_len > SSDFS_XATTR_EXTERNAL_BLOB_MAX_LEN) {
+				err = -ERANGE;
+				SSDFS_ERR("invalid blob_len %u\n",
+					  blob_len);
+				goto xattr_is_not_available;
+			}
+			break;
+
+		default:
+			err = -ERANGE;
+			SSDFS_ERR("unexpected blob type %#x\n",
+				  blob_type);
+			goto xattr_is_not_available;
+		}
+
+		if (value) {
+			switch (blob_type) {
+			case SSDFS_XATTR_INLINE_BLOB:
+				/* return value of attribute */
+				err = ssdfs_memcpy(value, 0, size,
+					     xattr->blob.inline_value.bytes,
+					     0, SSDFS_XATTR_INLINE_BLOB_MAX_LEN,
+					     blob_len);
+				if (unlikely(err)) {
+					SSDFS_ERR("fail to copy inline blob: "
+						  "err %zd\n", err);
+					goto xattr_is_not_available;
+				}
+				break;
+
+			case SSDFS_XATTR_REGULAR_BLOB:
+				err = ssdfs_xattr_read_external_blob(fsi,
+								     inode,
+								     xattr,
+								     value,
+								     size);
+				if (err == -EINTR) {
+					/*
+					 * Ignore this error.
+					 */
+					goto xattr_is_not_available;
+				} else if (unlikely(err)) {
+					SSDFS_ERR("fail to read external blob: "
+						  "err %zd\n", err);
+					goto xattr_is_not_available;
+				}
+				break;
+
+			default:
+				BUG();
+			}
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("BLOB DUMP:\n");
+			print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+					     value, size);
+			SSDFS_DBG("\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+		}
+
+		err = blob_len;
+
+xattr_is_not_available:
+		ssdfs_btree_search_free(search);
+
+finish_search_xattr:
+		up_read(&ii->lock);
+		break;
+
+	default:
+		err = -ENODATA;
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("xattrs tree is absent: "
+			  "ino %lu\n",
+			  (unsigned long)inode->i_ino);
+#endif /* CONFIG_SSDFS_DEBUG */
+		break;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("finished: err %zd\n", err);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	return err;
+}
+
+/*
+ * Create, replace or remove an extended attribute for this inode.  Value
+ * is NULL to remove an existing extended attribute, and non-NULL to
+ * either replace an existing extended attribute, or create a new extended
+ * attribute. The flags XATTR_REPLACE and XATTR_CREATE
+ * specify that an extended attribute must exist and must not exist
+ * previous to the call, respectively.
+ *
+ * Returns 0, or a negative error number on failure.
+ */
+int __ssdfs_setxattr(struct inode *inode, int name_index, const char *name,
+			const void *value, size_t size, int flags)
+{
+	struct ssdfs_fs_info *fsi = SSDFS_FS_I(inode->i_sb);
+	struct ssdfs_inode_info *ii = SSDFS_I(inode);
+	struct ssdfs_btree_search *search;
+	size_t name_len;
+	int private_flags;
+	u64 name_hash;
+	int err = 0;
+
+	if (name == NULL) {
+		SSDFS_ERR("name pointer is NULL\n");
+		return -EINVAL;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("name_index %d, name %s, value %p, "
+		  "size %zu, flags %#x\n",
+		  name_index, name, value, size, flags);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (value == NULL)
+		size = 0;
+
+	name_len = strlen(name);
+	if (name_len > SSDFS_MAX_NAME_LEN)
+		return -ERANGE;
+
+	private_flags = atomic_read(&ii->private_flags);
+
+	switch (private_flags) {
+	case SSDFS_INODE_HAS_INLINE_XATTR:
+	case SSDFS_INODE_HAS_XATTR_BTREE:
+		down_read(&ii->lock);
+
+		if (!ii->xattrs_tree) {
+			err = -ERANGE;
+			SSDFS_WARN("xattrs tree is absent!!!\n");
+			goto finish_setxattr;
+		}
+		break;
+
+	default:
+		down_write(&ii->lock);
+
+		if (ii->xattrs_tree) {
+			err = -ERANGE;
+			SSDFS_WARN("xattrs tree exists unexpectedly!!!\n");
+			goto finish_create_xattrs_tree;
+		} else {
+			err = ssdfs_xattrs_tree_create(fsi, ii);
+			if (unlikely(err)) {
+				SSDFS_ERR("fail to create the xattrs tree: "
+					  "ino %lu, err %d\n",
+					  inode->i_ino, err);
+				goto finish_create_xattrs_tree;
+			}
+
+			atomic_or(SSDFS_INODE_HAS_INLINE_XATTR,
+				  &ii->private_flags);
+		}
+
+finish_create_xattrs_tree:
+		downgrade_write(&ii->lock);
+
+		if (unlikely(err))
+			goto finish_setxattr;
+		break;
+	}
+
+	search = ssdfs_btree_search_alloc();
+	if (!search) {
+		err = -ENOMEM;
+		SSDFS_ERR("fail to allocate btree search object\n");
+		goto finish_setxattr;
+	}
+
+	ssdfs_btree_search_init(search);
+
+	name_hash = __ssdfs_generate_name_hash(name, name_len,
+					       SSDFS_XATTR_INLINE_NAME_MAX_LEN);
+	if (name_hash >= U64_MAX) {
+		err = -ERANGE;
+		SSDFS_ERR("invalid name hash\n");
+		goto clean_up;
+	}
+
+	if (value == NULL) {
+		/* remove value */
+		err = ssdfs_xattrs_tree_delete(ii->xattrs_tree,
+						name_hash,
+						name, name_len,
+						search);
+		if (err == -ENODATA) {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("unable to remove xattr: "
+				  "ino %lu, name %s, err %d\n",
+				  inode->i_ino, name, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+			goto clean_up;
+		} else if (unlikely(err)) {
+			SSDFS_ERR("fail to remove xattr: "
+				  "ino %lu, name %s, err %d\n",
+				  inode->i_ino, name, err);
+			goto clean_up;
+		}
+	} else if (flags & XATTR_CREATE) {
+		err = ssdfs_xattrs_tree_add(ii->xattrs_tree,
+					    name_index,
+					    name, name_len,
+					    value, size,
+					    ii,
+					    search);
+		if (err == -ENOSPC) {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("unable to create xattr: "
+				  "ino %lu, name %s, err %d\n",
+				  inode->i_ino, name, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+			goto clean_up;
+		} else if (unlikely(err)) {
+			SSDFS_ERR("fail to create xattr: "
+				  "ino %lu, name %s, err %d\n",
+				  inode->i_ino, name, err);
+			goto clean_up;
+		}
+	} else if (flags & XATTR_REPLACE) {
+		err = ssdfs_xattrs_tree_change(ii->xattrs_tree,
+						name_index,
+						name_hash,
+						name, name_len,
+						value, size,
+						search);
+		if (err == -ENOSPC) {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("unable to replace xattr: "
+				  "ino %lu, name %s, err %d\n",
+				  inode->i_ino, name, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+			goto clean_up;
+		} else if (unlikely(err)) {
+			SSDFS_ERR("fail to replace xattr: "
+				  "ino %lu, name %s, err %d\n",
+				  inode->i_ino, name, err);
+			goto clean_up;
+		}
+	} else {
+		err = ssdfs_xattrs_tree_delete(ii->xattrs_tree,
+						name_hash,
+						name, name_len,
+						search);
+		if (err == -ENODATA) {
+			err = 0;
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("no requested xattr in the tree: "
+				  "ino %lu, name %s\n",
+				  inode->i_ino, name);
+#endif /* CONFIG_SSDFS_DEBUG */
+		} else if (unlikely(err)) {
+			SSDFS_ERR("fail to remove xattr: "
+				  "ino %lu, name %s, err %d\n",
+				  inode->i_ino, name, err);
+			goto clean_up;
+		}
+
+		ssdfs_btree_search_init(search);
+
+		err = ssdfs_xattrs_tree_add(ii->xattrs_tree,
+					    name_index,
+					    name, name_len,
+					    value, size,
+					    ii,
+					    search);
+		if (err == -ENOSPC) {
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("unable to create xattr: "
+				  "ino %lu, name %s, err %d\n",
+				  inode->i_ino, name, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+			goto clean_up;
+		} else if (unlikely(err)) {
+			SSDFS_ERR("fail to create xattr: "
+				  "ino %lu, name %s, err %d\n",
+				  inode->i_ino, name, err);
+			goto clean_up;
+		}
+	}
+
+	inode_set_ctime_to_ts(inode, current_time(inode));
+	mark_inode_dirty(inode);
+
+clean_up:
+	ssdfs_btree_search_free(search);
+
+finish_setxattr:
+	up_read(&ii->lock);
+
+	return err;
+}
diff --git a/fs/ssdfs/xattr.h b/fs/ssdfs/xattr.h
new file mode 100644
index 000000000000..aeebfa42667a
--- /dev/null
+++ b/fs/ssdfs/xattr.h
@@ -0,0 +1,88 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ *
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/xattr.h - extended attributes support declarations.
+ *
+ * Copyright (c) 2014-2019 HGST, a Western Digital Company.
+ *              http://www.hgst.com/
+ * Copyright (c) 2014-2026 Viacheslav Dubeyko <slava@dubeyko.com>
+ *              http://www.ssdfs.org/
+ *
+ * (C) Copyright 2014-2019, HGST, Inc., All rights reserved.
+ *
+ * Created by HGST, San Jose Research Center, Storage Architecture Group
+ *
+ * Authors: Viacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Acknowledgement: Cyril Guyot
+ *                  Zvonimir Bandic
+ */
+
+#ifndef _SSDFS_XATTR_H
+#define _SSDFS_XATTR_H
+
+#include <linux/xattr.h>
+
+/* Name indexes */
+#define SSDFS_USER_XATTR_ID			1
+#define SSDFS_POSIX_ACL_ACCESS_XATTR_ID		2
+#define SSDFS_POSIX_ACL_DEFAULT_XATTR_ID	3
+#define SSDFS_TRUSTED_XATTR_ID			4
+#define SSDFS_SECURITY_XATTR_ID			5
+#define SSDFS_SYSTEM_XATTR_ID			6
+#define SSDFS_RICHACL_XATTR_ID			7
+#define SSDFS_XATTR_MAX_ID			255
+
+extern const struct xattr_handler ssdfs_xattr_user_handler;
+extern const struct xattr_handler ssdfs_xattr_trusted_handler;
+extern const struct xattr_handler ssdfs_xattr_security_handler;
+
+extern const struct xattr_handler *ssdfs_xattr_handlers[];
+
+ssize_t __ssdfs_getxattr(struct inode *, int, const char *, void *, size_t);
+
+static inline
+ssize_t ssdfs_getxattr(struct inode *inode,
+			int name_index, const char *name,
+			void *value, size_t size)
+{
+	return __ssdfs_getxattr(inode, name_index, name, value, size);
+}
+
+int __ssdfs_setxattr(struct inode *, int, const char *,
+			const void *, size_t, int);
+
+static inline
+int ssdfs_setxattr(struct inode *inode,
+		    int name_index, const char *name,
+		    const void *value, size_t size, int flags)
+{
+	return __ssdfs_setxattr(inode, name_index, name,
+				value, size, flags);
+}
+
+ssize_t ssdfs_listxattr(struct dentry *, char *, size_t);
+
+#ifdef CONFIG_SSDFS_SECURITY
+int ssdfs_init_security(struct inode *, struct inode *, const struct qstr *);
+int ssdfs_init_inode_security(struct inode *, struct inode *,
+				const struct qstr *);
+#else
+static inline
+int ssdfs_init_security(struct inode *inode, struct inode *dir,
+			const struct qstr *qstr)
+{
+	return 0;
+}
+
+static inline
+int ssdfs_init_inode_security(struct inode *inode, struct inode *dir,
+				const struct qstr *qstr)
+{
+	return 0;
+}
+#endif /* CONFIG_SSDFS_SECURITY */
+
+#endif /* _SSDFS_XATTR_H */
diff --git a/fs/ssdfs/xattr_security.c b/fs/ssdfs/xattr_security.c
new file mode 100644
index 000000000000..b5caff8ebbec
--- /dev/null
+++ b/fs/ssdfs/xattr_security.c
@@ -0,0 +1,159 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ *
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/xattr_security.c - handler for storing security labels as xattrs.
+ *
+ * Copyright (c) 2014-2019 HGST, a Western Digital Company.
+ *              http://www.hgst.com/
+ * Copyright (c) 2014-2026 Viacheslav Dubeyko <slava@dubeyko.com>
+ *              http://www.ssdfs.org/
+ *
+ * (C) Copyright 2014-2019, HGST, Inc., All rights reserved.
+ *
+ * Created by HGST, San Jose Research Center, Storage Architecture Group
+ *
+ * Authors: Viacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Acknowledgement: Cyril Guyot
+ *                  Zvonimir Bandic
+ */
+
+#include <linux/kernel.h>
+#include <linux/rwsem.h>
+#include <linux/security.h>
+#include <linux/pagevec.h>
+
+#include "peb_mapping_queue.h"
+#include "peb_mapping_table_cache.h"
+#include "folio_vector.h"
+#include "ssdfs.h"
+#include "xattr.h"
+#include "acl.h"
+
+static
+int ssdfs_security_getxattr(const struct xattr_handler *handler,
+			    struct dentry *unused, struct inode *inode,
+			    const char *name, void *buffer, size_t size)
+{
+	size_t len;
+
+	if (name == NULL || strcmp(name, "") == 0) {
+		SSDFS_ERR("invalid name\n");
+		return -EINVAL;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, name %s, buffer %p, size %zu\n",
+		  (unsigned long)inode->i_ino,
+		  name, buffer, size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	len = strlen(name);
+
+	if ((len + XATTR_SECURITY_PREFIX_LEN) > XATTR_NAME_MAX)
+		return -EOPNOTSUPP;
+
+	return ssdfs_getxattr(inode, SSDFS_SECURITY_XATTR_ID, name,
+				buffer, size);
+}
+
+static
+int ssdfs_security_setxattr(const struct xattr_handler *handler,
+			    struct mnt_idmap *idmap,
+			    struct dentry *unused, struct inode *inode,
+			    const char *name, const void *value,
+			    size_t size, int flags)
+{
+	size_t len;
+
+	if (name == NULL || strcmp(name, "") == 0) {
+		SSDFS_ERR("invalid name\n");
+		return -EINVAL;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, name %s, value %p, size %zu, flags %#x\n",
+		  (unsigned long)inode->i_ino,
+		  name, value, size, flags);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	len = strlen(name);
+
+	if ((len + XATTR_SECURITY_PREFIX_LEN) > XATTR_NAME_MAX)
+		return -EOPNOTSUPP;
+
+	return ssdfs_setxattr(inode, SSDFS_SECURITY_XATTR_ID, name,
+				value, size, flags);
+}
+
+static
+int ssdfs_initxattrs(struct inode *inode, const struct xattr *xattr_array,
+			void *fs_info)
+{
+	const struct xattr *xattr;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, xattr_array %p, fs_info %p\n",
+		  (unsigned long)inode->i_ino,
+		  xattr_array, fs_info);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	for (xattr = xattr_array; xattr->name != NULL; xattr++) {
+		size_t name_len;
+
+		name_len = strlen(xattr->name);
+
+		if (name_len == 0)
+			continue;
+
+		if (name_len + XATTR_SECURITY_PREFIX_LEN > XATTR_NAME_MAX)
+			return -EOPNOTSUPP;
+
+		err = __ssdfs_setxattr(inode, SSDFS_SECURITY_XATTR_ID,
+					xattr->name, xattr->value,
+					xattr->value_len, 0);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+int ssdfs_init_security(struct inode *inode, struct inode *dir,
+			const struct qstr *qstr)
+{
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("dir_ino %lu, ino %lu\n",
+		  (unsigned long)dir->i_ino,
+		  (unsigned long)inode->i_ino);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	return security_inode_init_security(inode, dir, qstr,
+					    &ssdfs_initxattrs, NULL);
+}
+
+int ssdfs_init_inode_security(struct inode *inode, struct inode *dir,
+				const struct qstr *qstr)
+{
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("dir_ino %lu, ino %lu\n",
+		  (unsigned long)dir->i_ino,
+		  (unsigned long)inode->i_ino);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	err = ssdfs_init_acl(inode, dir);
+	if (!err)
+		err = ssdfs_init_security(inode, dir, qstr);
+	return err;
+}
+
+const struct xattr_handler ssdfs_xattr_security_handler = {
+	.prefix	= XATTR_SECURITY_PREFIX,
+	.get	= ssdfs_security_getxattr,
+	.set	= ssdfs_security_setxattr,
+};
diff --git a/fs/ssdfs/xattr_tree.h b/fs/ssdfs/xattr_tree.h
new file mode 100644
index 000000000000..c8eaa779f240
--- /dev/null
+++ b/fs/ssdfs/xattr_tree.h
@@ -0,0 +1,143 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ *
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/xattr_tree.h - extended attributes btree declarations.
+ *
+ * Copyright (c) 2014-2019 HGST, a Western Digital Company.
+ *              http://www.hgst.com/
+ * Copyright (c) 2014-2026 Viacheslav Dubeyko <slava@dubeyko.com>
+ *              http://www.ssdfs.org/
+ *
+ * (C) Copyright 2014-2019, HGST, Inc., All rights reserved.
+ *
+ * Created by HGST, San Jose Research Center, Storage Architecture Group
+ *
+ * Authors: Viacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Acknowledgement: Cyril Guyot
+ *                  Zvonimir Bandic
+ */
+
+#ifndef _SSDFS_XATTR_TREE_H
+#define _SSDFS_XATTR_TREE_H
+
+/*
+ * struct ssdfs_xattrs_btree_info - xattrs btree info
+ * @type: xattrs btree type
+ * @state: xattrs btree state
+ * @lock: xattrs btree lock
+ * @generic_tree: pointer on generic btree object
+ * @inline_xattrs: pointer on inline xattrs array
+ * @inline_count: number of valid inline xattrs
+ * @inline_capacity: capacity of xattrs in the inline array
+ * @buffer.tree: piece of memory for generic btree object
+ * @buffer.xattr: piece of memory for the inline xattr
+ * @root: pointer on root node
+ * @root_buffer: buffer for root node
+ * @desc: b-tree descriptor
+ * @owner: pointer on owner inode object
+ * @fsi: pointer on shared file system object
+ */
+struct ssdfs_xattrs_btree_info {
+	atomic_t type;
+	atomic_t state;
+
+	struct rw_semaphore lock;
+	struct ssdfs_btree *generic_tree;
+	struct ssdfs_xattr_entry *inline_xattrs;
+	u16 inline_count;
+	u16 inline_capacity;
+
+	union {
+		struct ssdfs_btree tree;
+		struct ssdfs_xattr_entry xattr;
+	} buffer;
+	struct ssdfs_btree_inline_root_node *root;
+	struct ssdfs_btree_inline_root_node root_buffer;
+
+	struct ssdfs_xattr_btree_descriptor desc;
+	struct ssdfs_inode_info *owner;
+	struct ssdfs_fs_info *fsi;
+};
+
+/* Xattr tree types */
+enum {
+	SSDFS_XATTR_BTREE_UNKNOWN_TYPE,
+	SSDFS_INLINE_XATTR,
+	SSDFS_INLINE_XATTR_ARRAY,
+	SSDFS_PRIVATE_XATTR_BTREE,
+	SSDFS_XATTR_BTREE_TYPE_MAX
+};
+
+/* Xattr tree states */
+enum {
+	SSDFS_XATTR_BTREE_UNKNOWN_STATE,
+	SSDFS_XATTR_BTREE_CREATED,
+	SSDFS_XATTR_BTREE_INITIALIZED,
+	SSDFS_XATTR_BTREE_DIRTY,
+	SSDFS_XATTR_BTREE_CORRUPTED,
+	SSDFS_XATTR_BTREE_STATE_MAX
+};
+
+/*
+ * Xattr tree API
+ */
+int ssdfs_xattrs_tree_create(struct ssdfs_fs_info *fsi,
+			    struct ssdfs_inode_info *ii);
+int ssdfs_xattrs_tree_init(struct ssdfs_fs_info *fsi,
+			  struct ssdfs_inode_info *ii);
+void ssdfs_xattrs_tree_destroy(struct ssdfs_inode_info *ii);
+int ssdfs_xattrs_tree_flush(struct ssdfs_fs_info *fsi,
+			   struct ssdfs_inode_info *ii);
+
+int ssdfs_xattrs_tree_find(struct ssdfs_xattrs_btree_info *tree,
+			  const char *name, size_t len,
+			  struct ssdfs_btree_search *search);
+int ssdfs_xattrs_tree_add(struct ssdfs_xattrs_btree_info *tree,
+			 int name_index,
+			 const char *name, size_t name_len,
+			 const void *value, size_t size,
+			 struct ssdfs_inode_info *ii,
+			 struct ssdfs_btree_search *search);
+int ssdfs_xattrs_tree_change(struct ssdfs_xattrs_btree_info *tree,
+			    int name_index,
+			    u64 name_hash,
+			    const char *name, size_t name_len,
+			    const void *value, size_t size,
+			    struct ssdfs_btree_search *search);
+int ssdfs_xattrs_tree_delete(struct ssdfs_xattrs_btree_info *tree,
+			     u64 name_hash,
+			     const char *name, size_t name_len,
+			     struct ssdfs_btree_search *search);
+int ssdfs_xattrs_tree_delete_all(struct ssdfs_xattrs_btree_info *tree);
+
+/*
+ * Xattr tree internal API
+ */
+int __ssdfs_xattrs_btree_node_get_xattr(struct ssdfs_fs_info *fsi,
+					struct ssdfs_btree_node_content *content,
+					u32 area_offset,
+					u32 area_size,
+					u32 node_size,
+					u16 item_index,
+					struct ssdfs_xattr_entry *xattr);
+int ssdfs_xattrs_tree_find_leaf_node(struct ssdfs_xattrs_btree_info *tree,
+					u64 name_hash,
+					struct ssdfs_btree_search *search);
+int ssdfs_xattrs_tree_extract_range(struct ssdfs_xattrs_btree_info *tree,
+				    u16 start_index, u16 count,
+				    struct ssdfs_btree_search *search);
+
+void ssdfs_debug_xattrs_btree_object(struct ssdfs_xattrs_btree_info *tree);
+
+/*
+ * Xattr btree specialized operations
+ */
+extern const struct ssdfs_btree_descriptor_operations
+						ssdfs_xattrs_btree_desc_ops;
+extern const struct ssdfs_btree_operations ssdfs_xattrs_btree_ops;
+extern const struct ssdfs_btree_node_operations ssdfs_xattrs_btree_node_ops;
+
+#endif /* _SSDFS_XATTR_TREE_H */
diff --git a/fs/ssdfs/xattr_trusted.c b/fs/ssdfs/xattr_trusted.c
new file mode 100644
index 000000000000..8e1b0cc19bf2
--- /dev/null
+++ b/fs/ssdfs/xattr_trusted.c
@@ -0,0 +1,93 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ *
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/xattr_trusted.c - handler for trusted extended attributes.
+ *
+ * Copyright (c) 2014-2019 HGST, a Western Digital Company.
+ *              http://www.hgst.com/
+ * Copyright (c) 2014-2026 Viacheslav Dubeyko <slava@dubeyko.com>
+ *              http://www.ssdfs.org/
+ *
+ * (C) Copyright 2014-2019, HGST, Inc., All rights reserved.
+ *
+ * Created by HGST, San Jose Research Center, Storage Architecture Group
+ *
+ * Authors: Viacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Acknowledgement: Cyril Guyot
+ *                  Zvonimir Bandic
+ */
+
+#include <linux/kernel.h>
+#include <linux/rwsem.h>
+#include <linux/pagevec.h>
+
+#include "peb_mapping_queue.h"
+#include "peb_mapping_table_cache.h"
+#include "folio_vector.h"
+#include "ssdfs.h"
+#include "xattr.h"
+
+static
+int ssdfs_trusted_getxattr(const struct xattr_handler *handler,
+			   struct dentry *unused, struct inode *inode,
+			   const char *name, void *buffer, size_t size)
+{
+	size_t len;
+
+	if (name == NULL || strcmp(name, "") == 0) {
+		SSDFS_ERR("invalid name\n");
+		return -EINVAL;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, name %s, buffer %p, size %zu\n",
+		  (unsigned long)inode->i_ino,
+		  name, buffer, size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	len = strlen(name);
+
+	if ((len + XATTR_TRUSTED_PREFIX_LEN) > XATTR_NAME_MAX)
+		return -EOPNOTSUPP;
+
+	return ssdfs_getxattr(inode, SSDFS_TRUSTED_XATTR_ID, name,
+				buffer, size);
+}
+
+static
+int ssdfs_trusted_setxattr(const struct xattr_handler *handler,
+			   struct mnt_idmap *idmap,
+			   struct dentry *unused, struct inode *inode,
+			   const char *name, const void *value,
+			   size_t size, int flags)
+{
+	size_t len;
+
+	if (name == NULL || strcmp(name, "") == 0) {
+		SSDFS_ERR("invalid name\n");
+		return -EINVAL;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, name %s, value %p, size %zu, flags %#x\n",
+		  (unsigned long)inode->i_ino,
+		  name, value, size, flags);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	len = strlen(name);
+
+	if ((len + XATTR_TRUSTED_PREFIX_LEN) > XATTR_NAME_MAX)
+		return -EOPNOTSUPP;
+
+	return ssdfs_setxattr(inode, SSDFS_TRUSTED_XATTR_ID, name,
+				value, size, flags);
+}
+
+const struct xattr_handler ssdfs_xattr_trusted_handler = {
+	.prefix	= XATTR_TRUSTED_PREFIX,
+	.get	= ssdfs_trusted_getxattr,
+	.set	= ssdfs_trusted_setxattr,
+};
diff --git a/fs/ssdfs/xattr_user.c b/fs/ssdfs/xattr_user.c
new file mode 100644
index 000000000000..c643f5816dbb
--- /dev/null
+++ b/fs/ssdfs/xattr_user.c
@@ -0,0 +1,93 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ *
+ * SSDFS -- SSD-oriented File System.
+ *
+ * fs/ssdfs/xattr_user.c - handler for extended user attributes.
+ *
+ * Copyright (c) 2014-2019 HGST, a Western Digital Company.
+ *              http://www.hgst.com/
+ * Copyright (c) 2014-2026 Viacheslav Dubeyko <slava@dubeyko.com>
+ *              http://www.ssdfs.org/
+ *
+ * (C) Copyright 2014-2019, HGST, Inc., All rights reserved.
+ *
+ * Created by HGST, San Jose Research Center, Storage Architecture Group
+ *
+ * Authors: Viacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Acknowledgement: Cyril Guyot
+ *                  Zvonimir Bandic
+ */
+
+#include <linux/kernel.h>
+#include <linux/rwsem.h>
+#include <linux/pagevec.h>
+
+#include "peb_mapping_queue.h"
+#include "peb_mapping_table_cache.h"
+#include "folio_vector.h"
+#include "ssdfs.h"
+#include "xattr.h"
+
+static
+int ssdfs_user_getxattr(const struct xattr_handler *handler,
+			struct dentry *unused, struct inode *inode,
+			const char *name, void *buffer, size_t size)
+{
+	size_t len;
+
+	if (name == NULL || strcmp(name, "") == 0) {
+		SSDFS_ERR("invalid name\n");
+		return -EINVAL;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, name %s, buffer %p, size %zu\n",
+		  (unsigned long)inode->i_ino,
+		  name, buffer, size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	len = strlen(name);
+
+	if ((len + XATTR_USER_PREFIX_LEN) > XATTR_NAME_MAX)
+		return -EOPNOTSUPP;
+
+	return ssdfs_getxattr(inode, SSDFS_USER_XATTR_ID, name,
+				buffer, size);
+}
+
+static
+int ssdfs_user_setxattr(const struct xattr_handler *handler,
+			struct mnt_idmap *idmap,
+			struct dentry *unused, struct inode *inode,
+			const char *name, const void *value,
+			size_t size, int flags)
+{
+	size_t len;
+
+	if (name == NULL || strcmp(name, "") == 0) {
+		SSDFS_ERR("invalid name\n");
+		return -EINVAL;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("ino %lu, name %s, value %p, size %zu, flags %#x\n",
+		  (unsigned long)inode->i_ino,
+		  name, value, size, flags);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	len = strlen(name);
+
+	if ((len + XATTR_USER_PREFIX_LEN) > XATTR_NAME_MAX)
+		return -EOPNOTSUPP;
+
+	return ssdfs_setxattr(inode, SSDFS_USER_XATTR_ID, name,
+				value, size, flags);
+}
+
+const struct xattr_handler ssdfs_xattr_user_handler = {
+	.prefix	= XATTR_USER_PREFIX,
+	.get	= ssdfs_user_getxattr,
+	.set	= ssdfs_user_setxattr,
+};
-- 
2.34.1