From nobody Tue Apr 7 06:06:27 2026 Received: from mail-yw1-f170.google.com (mail-yw1-f170.google.com [209.85.128.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2B69732D0D8 for ; Mon, 16 Mar 2026 02:20:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773627612; cv=none; b=CZuZh0xr5c2KwRChdFVu0ZnzxLkJG03G4ZpDrAt3d3R3AZtB0GdviHpRxYnySWIQPI18gNIf+DQtlCZ6wc/tHJ6iBASm7Es0d8JIYd2s/8ionOrnp5MCuQZK6/Uph4WmCQuX4k7s7mGBnmpEAk92/7IYIMrts9Y1vP6OIX5WI04= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773627612; c=relaxed/simple; bh=1AC5Bo2shdM7re3S6ZVQ0Vs39gXHyoX3I5iaZmrsYec=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=n4SJ0axuqwcjvgj/YE85dB+hsH+Y506hm+souYPirtcFSbZ+NBGFZ/EMNqpwsg6CZNtt2cbBx/8IWmrFp2QIDlJgeYyNzdroHD+oTpKyYhgLlnna3Rp8v1xxuhKGc4MgodUZ6TZ0xa0OcvaKW02D+NYCRSeigvH0GALoplcnjTE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=dubeyko.com; spf=pass smtp.mailfrom=dubeyko.com; dkim=pass (2048-bit key) header.d=dubeyko-com.20230601.gappssmtp.com header.i=@dubeyko-com.20230601.gappssmtp.com header.b=L8SoJ71c; arc=none smtp.client-ip=209.85.128.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=dubeyko.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=dubeyko.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=dubeyko-com.20230601.gappssmtp.com header.i=@dubeyko-com.20230601.gappssmtp.com header.b="L8SoJ71c" Received: by mail-yw1-f170.google.com with SMTP id 00721157ae682-79885f4a8ffso32752067b3.3 for ; Sun, 15 Mar 2026 19:20:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dubeyko-com.20230601.gappssmtp.com; s=20230601; t=1773627602; x=1774232402; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=bGsMc+4ermqQp50Jj1pTftKv67j6a8JgPlKQ7HTC03w=; b=L8SoJ71cDg/1lWqIGp2IiNrIjnl8X8wKrWpbiL6ahM/lyndXsCzjCOIgjLVtJvqz6W 4qKZNLPfStLMrnvWiVYpL5coAP32RZLcRXj83AyNL3DyJNM4T6CW/gwUDUB3gMTllxjE rETY31wFvqxhlEm75e/iH0EVhZUGBQLQxJuDfpF4+RuiUdLii7q2oHeITscRVbi6tThe pq8jBN2bVp1kWyZTISapsfCkuSJJrwja04g8gtzuvwbI41BJVlnNK3bOALO9nAT8qFjM c7RF2beha78pR7LcvHB5d5yCQnjcm+1eijahdGHaAXRlRL5F31DI7H5ticfQpQ60ZZDh Hobg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773627602; x=1774232402; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=bGsMc+4ermqQp50Jj1pTftKv67j6a8JgPlKQ7HTC03w=; b=sCHYsJeYvLby4ebD8Gd9v9N8WBSwaU18s1Hh1BdibN29XrgcaAwcBYiAPyF1Wr2MCU rWN1WdN/l5vwD8hUQB9b3U5Aj9yF8GD8ILjhJvJRjgBauNbtJNAYVVkbU46Xrze3r3E2 5zGuH2LfP/FgWDhfcRi1CtPxuHsf5OddukN7Dnu797LJZbYF/7bN1SCn8BSFtCwntx29 SFJvEhMCT87g3mrYAWATx1ccAu2R60PT6vY5HlXFdzu+B01x5tYg1BU8KccZUl4pIiu3 DmcqTEVa20minn92EXf6MH3LFbzvtHwDEOnG0cz7LLVQfP4IgEHiy2rTAhnZxGNPtnq0 ZNpA== X-Gm-Message-State: AOJu0Yw5WwWe68m2SX+PI17AvVpTUm3021s0YWwjw6f8bd7wifd7ciQT mPPLxa1ef5UkzotXnSjUNk9Fszxl9isVRXGKtje/4GT6lKwBL3sNIaeuxsLxaoDa/PAZQUY1OLx 59nsc X-Gm-Gg: ATEYQzzZqrVjAp1+Tsmp8BozDYkax5QJ9+JHj8fvxzVzwkynkgmbiXmLjF6PhUNst0O Twa5O4aQVc+h6Z2THEpNiSB1A0zTuUhRL3SBOxVvnk/Gq/dcwS6gvhyvPVY1/LIHoESWt32T89F 6rlzOTkyIYCHmbIxGOQyH1jHOyPRTqRmxaT5OWOeVpZ0VS0/9jhYQC3aE47jGklz2+WDwIvb7EH 750l/vkNtK0oGNj1Tr1ryKprLAThnkY0EAs2T3MelOQcQP+AAGaOpQgz8Q4Vm2RJ1XwR7jqfvrq 2CjfZ30Dou+EuKuWnVJY541MoV9RwBmWi2J3S+UyprlO7EAm1ZyGhn3ibLKhvjCeEenza/JVexy RgzVbM8NwSQnU0TIO+JzTjVwYbJNrbQz4fJTNV541e5oG+XJNBIBD44sG7kHB9pJvrwfDqgU2Ly ChGRar7mvTp/FCs3zZI0xhQTV9xXRL/EWF4/qs98cfrVF8vonX2DuU3Qy1WYEhgzfsRv+Snz4QI FJGxE4uK8+GGx9CPeIuzk+5 X-Received: by 2002:a05:690c:e686:20b0:795:1cfa:5efb with SMTP id 00721157ae682-79a1c2023d2mr88778027b3.51.1773627601577; Sun, 15 Mar 2026 19:20:01 -0700 (PDT) Received: from pop-os.attlocal.net ([2600:1700:6476:1430:6939:3e01:5e8f:6093]) by smtp.gmail.com with ESMTPSA id 00721157ae682-79917deb69asm79721617b3.10.2026.03.15.19.20.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 15 Mar 2026 19:20:00 -0700 (PDT) From: Viacheslav Dubeyko To: linux-fsdevel@vger.kernel.org Cc: linux-kernel@vger.kernel.org, Viacheslav Dubeyko Subject: [PATCH v2 67/79] ssdfs: implement extended attributes support Date: Sun, 15 Mar 2026 19:17:56 -0700 Message-ID: <20260316021800.1694650-30-slava@dubeyko.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260316021800.1694650-1-slava@dubeyko.com> References: <20260316021800.1694650-1-slava@dubeyko.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- 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 + * 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 + * + * Acknowledgement: Cyril Guyot + * Zvonimir Bandic + */ + +#include +#include +#include + +#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) !=3D 0) { + SSDFS_ERR("ACL: " + "memory leaks include %lld folios\n", + atomic64_read(&ssdfs_acl_folio_leaks)); + } + + if (atomic64_read(&ssdfs_acl_memory_leaks) !=3D 0) { + SSDFS_ERR("ACL: " + "memory allocator suffers from %lld leaks\n", + atomic64_read(&ssdfs_acl_memory_leaks)); + } + + if (atomic64_read(&ssdfs_acl_cache_leaks) !=3D 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 =3D 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 =3D SSDFS_POSIX_ACL_ACCESS_XATTR_ID; + xattr_name =3D XATTR_NAME_POSIX_ACL_ACCESS; + break; + case ACL_TYPE_DEFAULT: + name_index =3D SSDFS_POSIX_ACL_DEFAULT_XATTR_ID; + xattr_name =3D XATTR_NAME_POSIX_ACL_DEFAULT; + break; + default: + SSDFS_ERR("unknown type %#x\n", type); + return ERR_PTR(-EINVAL); + } + + size =3D __ssdfs_getxattr(inode, name_index, xattr_name, NULL, 0); + + if (size > 0) { + value =3D ssdfs_acl_kzalloc(size, GFP_KERNEL); + if (unlikely(!value)) { + SSDFS_ERR("unable to allocate memory\n"); + return ERR_PTR(-ENOMEM); + } + size =3D __ssdfs_getxattr(inode, name_index, xattr_name, + value, size); + } + + if (size > 0) + acl =3D posix_acl_from_xattr(&init_user_ns, value, size); + else if (size =3D=3D -ENODATA) + acl =3D NULL; + else + acl =3D 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 =3D 0; + char *value =3D 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 =3D SSDFS_POSIX_ACL_ACCESS_XATTR_ID; + xattr_name =3D XATTR_NAME_POSIX_ACL_ACCESS; + break; + + case ACL_TYPE_DEFAULT: + name_index =3D SSDFS_POSIX_ACL_DEFAULT_XATTR_ID; + xattr_name =3D 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 =3D posix_acl_xattr_size(acl->a_count); + value =3D ssdfs_acl_kzalloc(size, GFP_KERNEL); + if (!value) { + SSDFS_ERR("unable to allocate memory\n"); + return -ENOMEM; + } + err =3D 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 =3D __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 =3D 0; + struct inode *inode =3D d_inode(dentry); + umode_t mode =3D 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 =3D=3D ACL_TYPE_ACCESS && acl) { + err =3D posix_acl_update_mode(idmap, inode, &mode, &acl); + if (err) + goto end_set_acl; + + if (mode !=3D inode->i_mode) + update_mode =3D 1; + } + + err =3D __ssdfs_set_acl(inode, acl, type); + if (!err && update_mode) { + inode->i_mode =3D 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 =3D 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 =3D posix_acl_create(dir, &inode->i_mode, &default_acl, &acl); + if (err) + return err; + + if (default_acl) { + err =3D __ssdfs_set_acl(inode, default_acl, ACL_TYPE_DEFAULT); + posix_acl_release(default_acl); + } + + if (acl) { + if (!err) + err =3D __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 + * 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 + * + * Acknowledgement: Cyril Guyot + * Zvonimir Bandic + */ + +#ifndef _SSDFS_ACL_H +#define _SSDFS_ACL_H + +#include + +#ifdef CONFIG_SSDFS_POSIX_ACL + +#define set_posix_acl_flag(sb) \ + ((sb)->s_flags |=3D 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 &=3D ~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 + * 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 + * + * Acknowledgement: Cyril Guyot + * Zvonimir Bandic + */ + +#include +#include +#include +#include + +#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[] =3D { + &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 =3D 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 =3D 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 =3D -ERANGE; + SSDFS_ERR("root node pointer is NULL\n"); + goto finish_get_start_hash; + } + + index =3D &tree->root->indexes[SSDFS_ROOT_NODE_LEFT_LEAF_NODE]; + *start_hash =3D 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 =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!tree || !search || !next_hash); +#endif /* CONFIG_SSDFS_DEBUG */ + + old_hash =3D 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 =3D 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 =3D 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 =3D *end_hash =3D U64_MAX; + *items_count =3D 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 =3D -ERANGE; + SSDFS_ERR("inline tree's pointer is empty\n"); + goto finish_process_inline_tree; + } + + inline_count =3D tree->inline_count; + + if (inline_count >=3D U16_MAX) { + err =3D -ERANGE; + SSDFS_ERR("unexpected xattrs count %u\n", + inline_count); + goto finish_process_inline_tree; + } + + *items_count =3D inline_count; + + if (*items_count =3D=3D 0) + goto finish_process_inline_tree; + + cur_xattr =3D &tree->inline_xattrs[0]; + *start_hash =3D le64_to_cpu(cur_xattr->name_hash); + + if (inline_count > tree->inline_capacity) { + err =3D -ERANGE; + SSDFS_ERR("xattrs_count %u > max_value %u\n", + inline_count, + tree->inline_capacity); + goto finish_process_inline_tree; + } + + cur_xattr =3D &tree->inline_xattrs[inline_count - 1]; + *end_hash =3D le64_to_cpu(cur_xattr->name_hash); + +finish_process_inline_tree: + up_read(&tree->lock); + break; + + case SSDFS_PRIVATE_XATTR_BTREE: + err =3D 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 *searc= h) +{ + size_t xattr_size =3D 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 >=3D U16_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + items_count =3D (u16)search->result.raw_buf.items_count; + + if (items_count =3D=3D 0) { + SSDFS_ERR("items_in_buffer %u\n", + items_count); + return -ENOENT; + } else if (items_count !=3D search->result.count) { + SSDFS_ERR("items_count %u !=3D search->result.count %u\n", + items_count, search->result.count); + return -ERANGE; + } + + buf_size =3D xattr_size * items_count; + + if (buf_size !=3D search->result.raw_buf.size) { + SSDFS_ERR("buf_size %zu !=3D 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) >=3D 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 <=3D SSDFS_XATTR_NAME_UNKNOWN_TYPE || + xattr->name_type >=3D 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 <=3D SSDFS_XATTR_BLOB_UNKNOWN_TYPE || + xattr->blob_type >=3D 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 =3D 0; + int err =3D 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 =3D le64_to_cpu(xattr->name_hash); + + if ((copied + xattr->name_len) >=3D 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 =3D 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 =3D + strlen(SSDFS_NS_PREFIX[SSDFS_USER_NS_INDEX]); + err =3D ssdfs_memcpy(buffer, offset, size, + SSDFS_NS_PREFIX[SSDFS_USER_NS_INDEX], + 0, prefix_len, + prefix_len); + BUG_ON(unlikely(err !=3D 0)); + offset +=3D prefix_len; + copied +=3D prefix_len; + break; + + case SSDFS_XATTR_TRUSTED_REGULAR_NAME: + prefix_len =3D + strlen(SSDFS_NS_PREFIX[SSDFS_TRUSTED_NS_INDEX]); + err =3D ssdfs_memcpy(buffer, offset, size, + SSDFS_NS_PREFIX[SSDFS_TRUSTED_NS_INDEX], + 0, prefix_len, + prefix_len); + BUG_ON(unlikely(err !=3D 0)); + offset +=3D prefix_len; + copied +=3D prefix_len; + break; + + case SSDFS_XATTR_SYSTEM_REGULAR_NAME: + prefix_len =3D + strlen(SSDFS_NS_PREFIX[SSDFS_SYSTEM_NS_INDEX]); + err =3D ssdfs_memcpy(buffer, offset, size, + SSDFS_NS_PREFIX[SSDFS_SYSTEM_NS_INDEX], + 0, prefix_len, + prefix_len); + BUG_ON(unlikely(err !=3D 0)); + offset +=3D prefix_len; + copied +=3D prefix_len; + break; + + case SSDFS_XATTR_SECURITY_REGULAR_NAME: + prefix_len =3D + strlen(SSDFS_NS_PREFIX[SSDFS_SECURITY_NS_INDEX]); + err =3D ssdfs_memcpy(buffer, offset, size, + SSDFS_NS_PREFIX[SSDFS_SECURITY_NS_INDEX], + 0, prefix_len, + prefix_len); + BUG_ON(unlikely(err !=3D 0)); + offset +=3D prefix_len; + copied +=3D prefix_len; + break; + + default: + SSDFS_ERR("unexpected name type %#x\n", + xattr->name_type); + return -EIO; + } + + err =3D ssdfs_memcpy(buffer, offset, size, + search->name.string.str, + 0, SSDFS_MAX_NAME_LEN, + search->name.string.len); + BUG_ON(unlikely(err !=3D 0)); + + offset +=3D search->name.string.len; + copied +=3D search->name.string.len; + + if (offset >=3D 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 =3D + strlen(SSDFS_NS_PREFIX[SSDFS_USER_NS_INDEX]); + err =3D ssdfs_memcpy(buffer, offset, size, + SSDFS_NS_PREFIX[SSDFS_USER_NS_INDEX], + 0, prefix_len, + prefix_len); + BUG_ON(unlikely(err !=3D 0)); + offset +=3D prefix_len; + copied +=3D prefix_len; + break; + + case SSDFS_XATTR_TRUSTED_INLINE_NAME: + prefix_len =3D + strlen(SSDFS_NS_PREFIX[SSDFS_TRUSTED_NS_INDEX]); + err =3D ssdfs_memcpy(buffer, offset, size, + SSDFS_NS_PREFIX[SSDFS_TRUSTED_NS_INDEX], + 0, prefix_len, + prefix_len); + BUG_ON(unlikely(err !=3D 0)); + offset +=3D prefix_len; + copied +=3D prefix_len; + break; + + case SSDFS_XATTR_SYSTEM_INLINE_NAME: + prefix_len =3D + strlen(SSDFS_NS_PREFIX[SSDFS_SYSTEM_NS_INDEX]); + err =3D ssdfs_memcpy(buffer, offset, size, + SSDFS_NS_PREFIX[SSDFS_SYSTEM_NS_INDEX], + 0, prefix_len, + prefix_len); + BUG_ON(unlikely(err !=3D 0)); + offset +=3D prefix_len; + copied +=3D prefix_len; + break; + + case SSDFS_XATTR_SECURITY_INLINE_NAME: + prefix_len =3D + strlen(SSDFS_NS_PREFIX[SSDFS_SECURITY_NS_INDEX]); + err =3D ssdfs_memcpy(buffer, offset, size, + SSDFS_NS_PREFIX[SSDFS_SECURITY_NS_INDEX], + 0, prefix_len, + prefix_len); + offset +=3D prefix_len; + copied +=3D prefix_len; + break; + + default: + SSDFS_ERR("unexpected name type %#x\n", + xattr->name_type); + return -EIO; + } + + name_len =3D xattr->name_len; + + err =3D ssdfs_memcpy(buffer, offset, size, + xattr->inline_string, + 0, SSDFS_XATTR_INLINE_NAME_MAX_LEN, + name_len); + BUG_ON(unlikely(err !=3D 0)); + + offset +=3D name_len; + copied +=3D name_len; + + if (offset >=3D 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 =3D 0; + size_t name_len =3D 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 =3D strlen(SSDFS_NS_PREFIX[SSDFS_USER_NS_INDEX]); + break; + + case SSDFS_XATTR_TRUSTED_INLINE_NAME: + case SSDFS_XATTR_TRUSTED_REGULAR_NAME: + prefix_len =3D strlen(SSDFS_NS_PREFIX[SSDFS_TRUSTED_NS_INDEX]); + break; + + case SSDFS_XATTR_SYSTEM_INLINE_NAME: + case SSDFS_XATTR_SYSTEM_REGULAR_NAME: + prefix_len =3D strlen(SSDFS_NS_PREFIX[SSDFS_SYSTEM_NS_INDEX]); + break; + + case SSDFS_XATTR_SECURITY_INLINE_NAME: + case SSDFS_XATTR_SECURITY_REGULAR_NAME: + prefix_len =3D strlen(SSDFS_NS_PREFIX[SSDFS_SECURITY_NS_INDEX]); + break; + + default: + /* do nothing */ + break; + } + + name_len =3D 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 =3D SSDFS_I(inode); + struct ssdfs_fs_info *fsi =3D SSDFS_FS_I(inode->i_sb); + struct ssdfs_shared_dict_btree_info *dict; + struct ssdfs_xattr_entry *xattr; + u8 *kaddr; + size_t xattr_size =3D sizeof(struct ssdfs_xattr_entry); + u16 items_count; + ssize_t res, copied =3D 0; + int i; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("ino %lu, buffer %p, size %zu\n", + inode->i_ino, buffer, size); +#endif /* CONFIG_SSDFS_DEBUG */ + + dict =3D fsi->shdictree; + if (!dict) { + SSDFS_ERR("shared dictionary is absent\n"); + return -ERANGE; + } + + down_read(&ii->lock); + + if (!ii->xattrs_tree) { + err =3D -ERANGE; + SSDFS_ERR("unexpected xattrs tree absence\n"); + goto finish_tree_processing; + } + + err =3D ssdfs_xattrs_tree_extract_range(ii->xattrs_tree, + 0, + SSDFS_DEFAULT_INLINE_XATTR_COUNT, + search); + if (err =3D=3D -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 =3D=3D -ENOENT) { + err =3D 0; + goto clean_up; + } else if (unlikely(err)) + goto clean_up; + + err =3D ssdfs_xattrs_tree_check_search_result(search); + if (unlikely(err)) { + SSDFS_ERR("corrupted search result: " + "err %d\n", err); + goto clean_up; + } + + items_count =3D search->result.count; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!search->result.raw_buf.place.ptr); +#endif /* CONFIG_SSDFS_DEBUG */ + + for (i =3D 0; i < items_count; i++) { + kaddr =3D (u8 *)search->result.raw_buf.place.ptr; + xattr =3D (struct ssdfs_xattr_entry *)(kaddr + (i * xattr_size)); + + if (is_invalid_xattr(xattr)) { + err =3D -EIO; + SSDFS_ERR("found corrupted xattr\n"); + goto clean_up; + } + + if (buffer) { + res =3D ssdfs_copy_name2buffer(dict, xattr, + search, copied, + buffer, size); + if (res < 0) { + err =3D res; + SSDFS_ERR("failed to copy name: " + "err %d\n", err); + goto clean_up; + } else + copied +=3D res + 1; + } else + copied +=3D 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 =3D SSDFS_I(inode); + struct ssdfs_fs_info *fsi =3D SSDFS_FS_I(inode->i_sb); + struct ssdfs_shared_dict_btree_info *dict; + struct ssdfs_xattr_entry *xattr; + u8 *kaddr; + size_t xattr_size =3D sizeof(struct ssdfs_xattr_entry); + u64 start_hash, end_hash; + u16 items_count; + ssize_t res, copied =3D 0; + int i; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("ino %lu, buffer %p, size %zu\n", + inode->i_ino, buffer, size); +#endif /* CONFIG_SSDFS_DEBUG */ + + dict =3D fsi->shdictree; + if (!dict) { + SSDFS_ERR("shared dictionary is absent\n"); + return -ERANGE; + } + + down_read(&ii->lock); + + if (!ii->xattrs_tree) { + err =3D -ERANGE; + SSDFS_ERR("unexpected xattrs tree absence\n"); + goto finish_get_start_hash; + } + + err =3D ssdfs_xattrs_tree_get_start_hash(ii->xattrs_tree, + &start_hash); + if (err =3D=3D -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 >=3D U64_MAX) { + err =3D -ERANGE; + SSDFS_ERR("invalid hash value\n"); + goto finish_get_start_hash; + } + +finish_get_start_hash: + up_read(&ii->lock); + + if (err =3D=3D -ENOENT) { + err =3D 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 =3D -ERESTARTSYS; + goto clean_up; + } + cond_resched(); + + down_read(&ii->lock); + + err =3D ssdfs_xattrs_tree_find_leaf_node(ii->xattrs_tree, + start_hash, + search); + if (err =3D=3D -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 =3D 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 =3D=3D 0) { + err =3D -ENOENT; + SSDFS_DBG("empty leaf node\n"); + goto finish_tree_processing; + } + + if (start_hash > end_hash) { + err =3D -ENOENT; + goto finish_tree_processing; + } + + err =3D 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 =3D=3D -ENOENT) { + err =3D 0; + goto clean_up; + } else if (unlikely(err)) + goto clean_up; + + err =3D ssdfs_xattrs_tree_check_search_result(search); + if (unlikely(err)) { + SSDFS_ERR("corrupted search result: " + "err %d\n", err); + goto clean_up; + } + + items_count =3D search->result.count; + + for (i =3D 0; i < items_count; i++) { + u64 hash; + + kaddr =3D (u8 *)search->result.raw_buf.place.ptr; + xattr =3D + (struct ssdfs_xattr_entry *)(kaddr + + (i * xattr_size)); + hash =3D le64_to_cpu(xattr->name_hash); + + if (is_invalid_xattr(xattr)) { + err =3D -EIO; + SSDFS_ERR("found corrupted xattr\n"); + goto clean_up; + } + + if (buffer) { + res =3D ssdfs_copy_name2buffer(dict, xattr, + search, copied, + buffer, size); + if (res < 0) { + err =3D res; + SSDFS_ERR("failed to copy name: " + "err %d\n", err); + goto clean_up; + } else + copied +=3D res + 1; + } else { + copied +=3D + ssdfs_calculate_name_length(xattr) + 1; + } + + start_hash =3D hash; + } + + if (start_hash !=3D end_hash) { + err =3D -ERANGE; + SSDFS_ERR("cur_hash %llx !=3D end_hash %llx\n", + start_hash, end_hash); + goto clean_up; + } + + start_hash =3D end_hash + 1; + + down_read(&ii->lock); + err =3D 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 =3D=3D -ENOENT) { + err =3D 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 =3D d_inode(dentry); + struct ssdfs_inode_info *ii =3D SSDFS_I(inode); + struct ssdfs_btree_search *search; + int private_flags; + ssize_t copied =3D 0; + int err =3D 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 =3D 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 =3D ssdfs_btree_search_alloc(); + if (!search) { + SSDFS_ERR("fail to allocate btree search object\n"); + return -ENOMEM; + } + + if (!ii->xattrs_tree) { + err =3D -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 =3D ssdfs_listxattr_inline_tree(inode, search, + buffer, size); + if (unlikely(copied < 0)) { + err =3D copied; + SSDFS_ERR("fail to extract the inline range: " + "err %d\n", err); + goto clean_up; + } + break; + + case SSDFS_PRIVATE_XATTR_BTREE: + copied =3D ssdfs_listxattr_generic_tree(inode, search, + buffer, size); + if (unlikely(copied < 0)) { + err =3D copied; + SSDFS_ERR("fail to extract the range: " + "err %d\n", err); + goto clean_up; + } + break; + + default: + err =3D -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 =3D 0; + struct completion *end; + int i, j; + int err =3D 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!fsi || !inode || !xattr || !value); +#endif /* CONFIG_SSDFS_DEBUG */ + + seg_id =3D le64_to_cpu(xattr->blob.descriptor.extent.seg_id); + logical_blk =3D le32_to_cpu(xattr->blob.descriptor.extent.logical_blk); + len =3D 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 =3D=3D U64_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + ssdfs_segment_search_state_init(&seg_search, + SSDFS_USER_DATA_SEG_TYPE, + seg_id, U64_MAX); + + si =3D ssdfs_grab_segment(fsi, &seg_search); + if (unlikely(IS_ERR_OR_NULL(si))) { + err =3D !si ? -ENOMEM : PTR_ERR(si); + if (err =3D=3D -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 =3D 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 =3D le16_to_cpu(xattr->blob_len); + + if (blob_size > size) { + err =3D -EINVAL; + SSDFS_ERR("invalid request: blob_size %u > size %zu\n", + blob_size, size); + goto finish_prepare_request; + } + + batch_size =3D blob_size >> fsi->log_pagesize; + + if (batch_size =3D=3D 0) + batch_size =3D 1; + + if (batch_size > SSDFS_EXTENT_LEN_MAX) { + err =3D -ERANGE; + SSDFS_WARN("invalid memory folios count: " + "blob_size %u, batch_size %u\n", + blob_size, batch_size); + goto finish_prepare_request; + } + + req =3D ssdfs_request_alloc(); + if (IS_ERR_OR_NULL(req)) { + err =3D (req =3D=3D 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 =3D 0; + data_bytes =3D blob_size; + ssdfs_request_prepare_logical_extent(inode->i_ino, + (u64)logical_offset, + (u32)data_bytes, + 0, 0, req); + + for (i =3D 0; i < batch_size; i++) { + err =3D 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 >=3D U16_MAX); + BUG_ON(len >=3D 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 =3D si->blk2off_table; + + err =3D 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 =3D &si->peb_array[pos.peb_index]; + + err =3D ssdfs_peb_readahead_pages(pebc, req, &end); + if (err =3D=3D -EAGAIN) { + err =3D SSDFS_WAIT_COMPLETION(end); + if (unlikely(err)) { + SSDFS_ERR("PEB init failed: " + "err %d\n", err); + goto fail_read_blob; + } + + err =3D 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 =3D 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 =3D=3D 0); + + for (i =3D 0; i < req->result.content.count; i++) { + block =3D &req->result.content.blocks[i]; + blk_state =3D &block->new_state; + + BUG_ON(folio_batch_count(&blk_state->batch) =3D=3D 0); + + for (j =3D 0; j < folio_batch_count(&blk_state->batch); j++) { + void *kaddr; + u32 processed_bytes =3D 0; + u32 page_index =3D 0; + + folio =3D blk_state->batch.folios[j]; + + WARN_ON(!folio_test_locked(folio)); + + do { + kaddr =3D 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 +=3D PAGE_SIZE; + page_index++; + } while (processed_bytes < folio_size(folio)); + } + } +#endif /* CONFIG_SSDFS_DEBUG */ + + for (i =3D 0; i < req->result.content.count; i++) { + block =3D &req->result.content.blocks[i]; + blk_state =3D &block->new_state; + + for (j =3D 0; j < folio_batch_count(&blk_state->batch); j++) { + u32 cur_len; + + folio =3D blk_state->batch.folios[j]; + + if (copied_bytes >=3D blob_size) + goto finish_copy_operation; + + cur_len =3D min_t(u32, (u32)folio_size(folio), + blob_size - copied_bytes); + + err =3D __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 +=3D 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 =3D SSDFS_FS_I(inode->i_sb); + struct ssdfs_inode_info *ii =3D 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 =3D 0; + + if (name =3D=3D 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 =3D strlen(name); + if (name_len > SSDFS_MAX_NAME_LEN) + return -ERANGE; + + private_flags =3D 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 =3D -ERANGE; + SSDFS_WARN("xattrs tree is absent!!!\n"); + goto finish_search_xattr; + } + + search =3D ssdfs_btree_search_alloc(); + if (!search) { + err =3D -ENOMEM; + SSDFS_ERR("fail to allocate btree search object\n"); + goto finish_search_xattr; + } + + ssdfs_btree_search_init(search); + + err =3D ssdfs_xattrs_tree_find(ii->xattrs_tree, + name, name_len, + search); + + if (err =3D=3D -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 !=3D SSDFS_BTREE_SEARCH_VALID_ITEM) { + err =3D -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 =3D -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 =3D -ERANGE; + SSDFS_ERR("buffer is absent\n"); + goto xattr_is_not_available; + } + + if (search->result.raw_buf.size =3D=3D 0) { + err =3D -ERANGE; + SSDFS_ERR("result.buf_size %zu\n", + search->result.raw_buf.size); + goto xattr_is_not_available; + } + + kaddr =3D (u8 *)search->result.raw_buf.place.ptr; + xattr =3D (struct ssdfs_xattr_entry *)kaddr; + + blob_len =3D le16_to_cpu(xattr->blob_len); + blob_type =3D xattr->blob_type; + blob_flags =3D xattr->blob_flags; + + switch (blob_type) { + case SSDFS_XATTR_INLINE_BLOB: + if (blob_len > SSDFS_XATTR_INLINE_BLOB_MAX_LEN) { + err =3D -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 =3D -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 =3D -ERANGE; + SSDFS_ERR("invalid blob_len %u\n", + blob_len); + goto xattr_is_not_available; + } + break; + + default: + err =3D -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 =3D 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 =3D ssdfs_xattr_read_external_blob(fsi, + inode, + xattr, + value, + size); + if (err =3D=3D -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 =3D blob_len; + +xattr_is_not_available: + ssdfs_btree_search_free(search); + +finish_search_xattr: + up_read(&ii->lock); + break; + + default: + err =3D -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 =3D SSDFS_FS_I(inode->i_sb); + struct ssdfs_inode_info *ii =3D SSDFS_I(inode); + struct ssdfs_btree_search *search; + size_t name_len; + int private_flags; + u64 name_hash; + int err =3D 0; + + if (name =3D=3D 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 =3D=3D NULL) + size =3D 0; + + name_len =3D strlen(name); + if (name_len > SSDFS_MAX_NAME_LEN) + return -ERANGE; + + private_flags =3D 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 =3D -ERANGE; + SSDFS_WARN("xattrs tree is absent!!!\n"); + goto finish_setxattr; + } + break; + + default: + down_write(&ii->lock); + + if (ii->xattrs_tree) { + err =3D -ERANGE; + SSDFS_WARN("xattrs tree exists unexpectedly!!!\n"); + goto finish_create_xattrs_tree; + } else { + err =3D 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 =3D ssdfs_btree_search_alloc(); + if (!search) { + err =3D -ENOMEM; + SSDFS_ERR("fail to allocate btree search object\n"); + goto finish_setxattr; + } + + ssdfs_btree_search_init(search); + + name_hash =3D __ssdfs_generate_name_hash(name, name_len, + SSDFS_XATTR_INLINE_NAME_MAX_LEN); + if (name_hash >=3D U64_MAX) { + err =3D -ERANGE; + SSDFS_ERR("invalid name hash\n"); + goto clean_up; + } + + if (value =3D=3D NULL) { + /* remove value */ + err =3D ssdfs_xattrs_tree_delete(ii->xattrs_tree, + name_hash, + name, name_len, + search); + if (err =3D=3D -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 =3D ssdfs_xattrs_tree_add(ii->xattrs_tree, + name_index, + name, name_len, + value, size, + ii, + search); + if (err =3D=3D -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 =3D ssdfs_xattrs_tree_change(ii->xattrs_tree, + name_index, + name_hash, + name, name_len, + value, size, + search); + if (err =3D=3D -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 =3D ssdfs_xattrs_tree_delete(ii->xattrs_tree, + name_hash, + name, name_len, + search); + if (err =3D=3D -ENODATA) { + err =3D 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 =3D ssdfs_xattrs_tree_add(ii->xattrs_tree, + name_index, + name, name_len, + value, size, + ii, + search); + if (err =3D=3D -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 + * 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 + * + * Acknowledgement: Cyril Guyot + * Zvonimir Bandic + */ + +#ifndef _SSDFS_XATTR_H +#define _SSDFS_XATTR_H + +#include + +/* 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 xatt= rs. + * + * Copyright (c) 2014-2019 HGST, a Western Digital Company. + * http://www.hgst.com/ + * Copyright (c) 2014-2026 Viacheslav Dubeyko + * 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 + * + * Acknowledgement: Cyril Guyot + * Zvonimir Bandic + */ + +#include +#include +#include +#include + +#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 =3D=3D NULL || strcmp(name, "") =3D=3D 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 =3D 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 =3D=3D NULL || strcmp(name, "") =3D=3D 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 =3D 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 =3D xattr_array; xattr->name !=3D NULL; xattr++) { + size_t name_len; + + name_len =3D strlen(xattr->name); + + if (name_len =3D=3D 0) + continue; + + if (name_len + XATTR_SECURITY_PREFIX_LEN > XATTR_NAME_MAX) + return -EOPNOTSUPP; + + err =3D __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 =3D ssdfs_init_acl(inode, dir); + if (!err) + err =3D ssdfs_init_security(inode, dir, qstr); + return err; +} + +const struct xattr_handler ssdfs_xattr_security_handler =3D { + .prefix =3D XATTR_SECURITY_PREFIX, + .get =3D ssdfs_security_getxattr, + .set =3D 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 + * 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 + * + * 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_op= s; + +#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 + * 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 + * + * Acknowledgement: Cyril Guyot + * Zvonimir Bandic + */ + +#include +#include +#include + +#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 =3D=3D NULL || strcmp(name, "") =3D=3D 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 =3D 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 =3D=3D NULL || strcmp(name, "") =3D=3D 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 =3D 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 =3D { + .prefix =3D XATTR_TRUSTED_PREFIX, + .get =3D ssdfs_trusted_getxattr, + .set =3D 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 + * 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 + * + * Acknowledgement: Cyril Guyot + * Zvonimir Bandic + */ + +#include +#include +#include + +#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 =3D=3D NULL || strcmp(name, "") =3D=3D 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 =3D 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 =3D=3D NULL || strcmp(name, "") =3D=3D 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 =3D 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 =3D { + .prefix =3D XATTR_USER_PREFIX, + .get =3D ssdfs_user_getxattr, + .set =3D ssdfs_user_setxattr, +}; --=20 2.34.1