[PATCH 20/23] Add set/get/remove xattrs with up to 503 bytes of name + value
Valerie Aurora
val at versity.com
Fri Apr 4 11:45:36 PDT 2025
Use the native btree items to implement short xattrs of up to 503
bytes including both name and value (no null terminators). Add the
debugfs commands to set, get, and remove xattrs with create and
replace flags.
Signed-off-by: Valerie Aurora <val at versity.com>
---
cli/debugfs.c | 112 +++++++++++++
shared/format-block.h | 8 +
shared/inode.c | 3 +-
shared/lk/xattr.h | 10 ++
shared/xattr.c | 369 ++++++++++++++++++++++++++++++++++++++++++
shared/xattr.h | 14 ++
6 files changed, 515 insertions(+), 1 deletion(-)
create mode 100644 shared/lk/xattr.h
create mode 100644 shared/xattr.c
create mode 100644 shared/xattr.h
diff --git a/cli/debugfs.c b/cli/debugfs.c
index 29a38c3..2ea1fb5 100644
--- a/cli/debugfs.c
+++ b/cli/debugfs.c
@@ -15,6 +15,7 @@
#include "shared/lk/err.h"
#include "shared/lk/kernel.h"
#include "shared/lk/types.h"
+#include "shared/lk/xattr.h"
#include "shared/dir.h"
#include "shared/format-block.h"
@@ -25,6 +26,7 @@
#include "shared/nerr.h"
#include "shared/parse.h"
#include "shared/thread.h"
+#include "shared/xattr.h"
#include "cli/cli.h"
@@ -190,6 +192,46 @@ static void cmd_create(struct debugfs_context *ctx, int argc, char **argv)
print_err("create", ret);
}
+static void cmd_getxattr(struct debugfs_context *ctx, int argc, char **argv)
+{
+ struct ngnfs_dir_lookup_entry lent;
+ char *filename, *xname, *value;
+ size_t val_size = NGNFS_BTREE_MAX_VAL_SIZE;
+ int ret;
+
+ if (argc != 3) {
+ printf("usage: getxattr <filename> <xattr name>\n");
+ return;
+ }
+
+ filename = argv[1];
+ xname = argv[2];
+
+ value = malloc(val_size);
+ if (!value) {
+ printf("malloc error");
+ return;
+ }
+
+ ret = ngnfs_dir_lookup(ctx->nfi, &ctx->cwd_ig, filename, strlen(filename), &lent);
+ if (ret < 0) {
+ print_err("getxattr", ret);
+ goto out;
+ }
+
+ ret = ngnfs_xattr_get(ctx->nfi, &lent.ig, xname, value, val_size);
+ if (ret < 0) {
+ print_err("getxattr", ret);
+ goto out;
+ }
+
+ printf("%.*s\n", ret, value);
+
+out:
+ free(value);
+ return;
+}
+
static void cmd_lookup(struct debugfs_context *ctx, int argc, char **argv)
{
struct ngnfs_dir_lookup_entry lent;
@@ -314,6 +356,33 @@ static void cmd_readdir(struct debugfs_context *ctx, int argc, char **argv)
free(buf);
}
+static void cmd_removexattr(struct debugfs_context *ctx, int argc, char **argv)
+{
+ struct ngnfs_dir_lookup_entry lent;
+ char *filename, *xname;
+ int ret;
+
+ if (argc != 3) {
+ printf("usage: removexattr <filename> <xattr name>\n");
+ return;
+ }
+
+ filename = argv[1];
+ xname = argv[2];
+
+ ret = ngnfs_dir_lookup(ctx->nfi, &ctx->cwd_ig, filename, strlen(filename), &lent);
+ if (ret < 0) {
+ print_err("removexattr", ret);
+ goto out;
+ }
+
+ ret = ngnfs_xattr_remove(ctx->nfi, &lent.ig, xname);
+ if (ret < 0)
+ print_err("removexattr", ret);
+out:
+ return;
+}
+
/*
* Basic rename hack for now.
*
@@ -359,6 +428,46 @@ static void cmd_rmdir(struct debugfs_context *ctx, int argc, char **argv)
print_err("rmdir", ret);
}
+static void cmd_setxattr(struct debugfs_context *ctx, int argc, char **argv)
+{
+ struct ngnfs_dir_lookup_entry lent;
+ char *filename, *xname, *value;
+ int flags;
+ int ret;
+
+ if ((argc < 4) || (argc > 5)) {
+ printf("usage: setxattr <filename> <xattr name> <xattr value> [create|replace]\n");
+ return;
+ }
+
+ filename = argv[1];
+ xname = argv[2];
+ value = argv[3];
+ flags = 0;
+ if (argc == 5) {
+ if (strcmp(argv[4], "create") == 0)
+ flags |= XATTR_CREATE;
+ else if (strcmp(argv[4], "replace") == 0)
+ flags |= XATTR_REPLACE;
+ else {
+ printf("setxattr: invalid mode %s\n", argv[4]);
+ return;
+ }
+ }
+
+ ret = ngnfs_dir_lookup(ctx->nfi, &ctx->cwd_ig, filename, strlen(filename), &lent);
+ if (ret < 0) {
+ print_err("setxattr", ret);
+ return;
+ }
+
+ ret = ngnfs_xattr_set(ctx->nfi, &lent.ig, xname, value, strlen(value), flags);
+ if (ret < 0)
+ print_err("setxattr", ret);
+
+ return;
+}
+
static void cmd_stat(struct debugfs_context *ctx, int argc, char **argv)
{
struct ngnfs_inode_ino_gen ig = ctx->cwd_ig;
@@ -449,13 +558,16 @@ static struct command {
{ "brename", cmd_brename, },
{ "cd", cmd_cd, },
{ "create", cmd_create, },
+ { "getxattr", cmd_getxattr, },
{ "lookup", cmd_lookup, },
{ "mkdir", cmd_mkdir, },
{ "mkfs", cmd_mkfs, },
{ "quit", cmd_quit, },
{ "readdir", cmd_readdir, },
+ { "removexattr", cmd_removexattr, },
{ "rename", cmd_rename, },
{ "rmdir", cmd_rmdir, },
+ { "setxattr", cmd_setxattr, },
{ "stat", cmd_stat, },
{ "sync", cmd_sync, },
{ "unlink", cmd_unlink, },
diff --git a/shared/format-block.h b/shared/format-block.h
index d3cc302..12d8bfc 100644
--- a/shared/format-block.h
+++ b/shared/format-block.h
@@ -117,6 +117,7 @@ struct ngnfs_inode {
__le64 mtime_nsec;
__le64 crtime_nsec;
struct ngnfs_btree_root dirents;
+ struct ngnfs_btree_root xattrs;
};
#define NGNFS_ROOT_INO 1
@@ -172,4 +173,11 @@ struct ngnfs_dirent {
#define NGNFS_DIRENT_DOT_DOT_HASH 1ULL
#define NGNFS_DIRENT_MIN_HASH 2ULL
+struct ngnfs_xattr {
+ __le16 val_len;
+ __u8 name_len;
+ __u8 __pad[5];
+ __u8 name[];
+};
+
#endif
diff --git a/shared/inode.c b/shared/inode.c
index 5a4d14b..b147980 100644
--- a/shared/inode.c
+++ b/shared/inode.c
@@ -62,7 +62,8 @@ int ngnfs_inode_init(struct ngnfs_inode_txn_ref *itref, struct ngnfs_inode_ino_g
ngnfs_tblk_assign(tblk, ninode->ctime_nsec, ninode->atime_nsec);
ngnfs_tblk_assign(tblk, ninode->mtime_nsec, ninode->atime_nsec);
ngnfs_tblk_assign(tblk, ninode->crtime_nsec, ninode->atime_nsec);
- ngnfs_tblk_memset(tblk, &ninode->dirents, 0, sizeof(struct ngnfs_btree_root));
+ ngnfs_tblk_memset(tblk, &ninode->dirents, 0, sizeof(ninode->dirents));
+ ngnfs_tblk_memset(tblk, &ninode->xattrs, 0, sizeof(ninode->xattrs));
return 0;
}
diff --git a/shared/lk/xattr.h b/shared/lk/xattr.h
new file mode 100644
index 0000000..062ef43
--- /dev/null
+++ b/shared/lk/xattr.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef NGNFS_LK_XATTR_H
+#define NGNFS_LK_XATTR_H
+
+#define XATTR_CREATE 0x1 /* set value, fail if attr already exists */
+#define XATTR_REPLACE 0x2 /* set value, fail if attr does not exist */
+
+#define XATTR_NAME_MAX 255
+
+#endif
diff --git a/shared/xattr.c b/shared/xattr.c
new file mode 100644
index 0000000..6a0fafe
--- /dev/null
+++ b/shared/xattr.c
@@ -0,0 +1,369 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include "shared/lk/byteorder.h"
+#include "shared/lk/errno.h"
+#include "shared/lk/types.h"
+#include "shared/lk/xattr.h"
+#include "shared/lk/xxhash.h"
+
+#include "shared/block.h"
+#include "shared/btree.h"
+#include "shared/format-block.h"
+#include "shared/inode.h"
+#include "shared/txn.h"
+#include "shared/xattr.h"
+
+/*
+ * Maximum size of both xattr name and value added together.
+ *
+ * TODO: support much larger xattrs by storing in blocks instead of
+ * btree items.
+ */
+#define NGNFS_XATTR_MAX_SIZE NGNFS_BTREE_MAX_VAL_SIZE
+
+static u64 xattr_hash(void *name, size_t name_len)
+{
+ /* XXX using the same #defines as dirents, should be different? */
+ return xxh64(name, name_len, NGNFS_DIRENT_HASH_SEED) & NGNFS_DIRENT_HASH_MASK;
+}
+
+struct xattr_args {
+ struct ngnfs_inode_txn_ref *ino;
+ u64 hash;
+ char *name;
+ char *value;
+ struct ngnfs_xattr *xattr;
+ size_t name_len;
+ size_t val_size;
+ size_t xa_size;
+ int flags;
+ bool found;
+};
+
+static size_t xattr_size(size_t name_len, size_t val_size) {
+ return offsetof(struct ngnfs_xattr, name) + name_len + val_size;
+}
+
+static void init_xattr_args(struct xattr_args *xa, struct ngnfs_inode_txn_ref *ino,
+ char *name, size_t name_len, void *value, size_t val_size,
+ struct ngnfs_xattr *xattr, int flags)
+{
+ xa->ino = ino;
+ xa->hash = xattr_hash(name, name_len);
+ xa->name = name;
+ xa->value = value;
+ xa->xattr = xattr;
+ xa->name_len = name_len;
+ xa->val_size = val_size;
+ xa->found = false;
+ xa->flags = flags;
+
+ if (xattr) {
+ xa->xa_size = xattr_size(name_len, val_size);
+ xattr->name_len = name_len;
+ xattr->val_len = cpu_to_le16(val_size);
+ memcpy(xattr->name, name, name_len);
+ memcpy(xattr->name + name_len, value, val_size);
+ } else {
+ xa->xa_size = 0;
+ }
+
+}
+
+static void init_xattr_key(struct ngnfs_btree_key *key, u64 hash)
+{
+ *key = (struct ngnfs_btree_key) {
+ .k[0] = cpu_to_le64(hash),
+ };
+}
+
+static bool xattr_names_equal(u8 *a, size_t a_len, u8 *b, size_t b_len)
+{
+ return a_len == b_len && memcmp(a, b, a_len) == 0;
+}
+
+static int fill_xattr_rd(struct ngnfs_btree_key *key, void *val, size_t val_size, void *arg)
+{
+ struct xattr_args *xa = arg;
+ struct ngnfs_xattr *xattr = val;
+
+ if (!xattr_names_equal(xattr->name, xattr->name_len, (u8 *) xa->name, xa->name_len))
+ return NGNFS_BTREE_ITER_CONTINUE;
+
+ if (le16_to_cpu(xattr->val_len) > xa->val_size)
+ return -ENOBUFS;
+
+ memcpy(xa->value, xattr->name + xattr->name_len, le16_to_cpu(xattr->val_len));
+ xa->val_size = le16_to_cpu(xattr->val_len);
+ xa->found = true;
+
+ return 0;
+}
+
+static int get_xattr(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn,
+ struct ngnfs_inode_txn_ref *ino, struct xattr_args *xa)
+{
+ struct ngnfs_btree_key key;
+ struct ngnfs_btree_key last;
+ int ret;
+
+ init_xattr_key(&key, xa->hash);
+ init_xattr_key(&last, xa->hash | NGNFS_DIRENT_COLL_BIT);
+
+ ret = ngnfs_btree_read_iter(nfi, txn, &ino->ninode->xattrs, &key, NULL, &last,
+ fill_xattr_rd, xa);
+
+ if (ret < 0)
+ goto out;
+
+ if (!xa->found)
+ ret = -ENODATA;
+out:
+ return ret;
+}
+
+ssize_t ngnfs_xattr_get(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *ig, char *name,
+ void *value, size_t val_size)
+{
+ struct ngnfs_transaction txn;
+ struct ngnfs_inode_txn_ref ino;
+ struct xattr_args xa;
+ size_t name_len;
+ int ret;
+
+ name_len = strlen(name);
+ if (name_len > XATTR_NAME_MAX)
+ return -ERANGE;
+
+ ngnfs_txn_init(&txn);
+ init_xattr_args(&xa, &ino, name, name_len, value, val_size, NULL, 0);
+
+ do {
+ ret = ngnfs_inode_get(nfi, &txn, NBF_READ, ig, &ino) ?:
+ get_xattr(nfi, &txn, &ino, &xa);
+
+ } while (ngnfs_txn_retry(nfi, &txn, &ret));
+
+ ngnfs_txn_teardown(nfi, &txn);
+
+ if (ret == 0)
+ ret = xa.val_size;
+
+ return ret;
+}
+
+static int remove_xattr_wr(struct ngnfs_btree_key *key, void *val, size_t val_size, void *arg,
+ struct ngnfs_btree_op *op)
+{
+ struct xattr_args *xa = arg;
+ struct ngnfs_xattr *xattr = val;
+
+ if (!xattr)
+ return -ENODATA;
+
+ if (!xattr_names_equal(xattr->name, xattr->name_len, (u8 *) xa->name, xa->name_len))
+ return NGNFS_BTREE_ITER_CONTINUE;
+
+ op->delete = 1;
+ xa->found = true;
+
+ return 0;
+}
+
+static int remove_xattr(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn,
+ struct xattr_args *xa)
+{
+ struct ngnfs_btree_key key;
+ struct ngnfs_btree_key last;
+ int ret;
+
+ init_xattr_key(&key, xa->hash);
+ init_xattr_key(&last, xa->hash | NGNFS_DIRENT_COLL_BIT);
+
+ ret = ngnfs_btree_write_iter(nfi, txn, xa->ino->tblk, &xa->ino->ninode->xattrs, &key,
+ &last, remove_xattr_wr, xa);
+
+ if ((ret == 0) && (!xa->found))
+ ret = -ENODATA;
+
+ return ret;
+}
+
+int ngnfs_xattr_remove(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *ig, char *name)
+{
+ struct ngnfs_transaction txn;
+ struct ngnfs_inode_txn_ref ino;
+ struct xattr_args xa;
+ size_t name_len;
+ int ret;
+
+ name_len = strlen(name);
+ if (name_len > XATTR_NAME_MAX)
+ return -ERANGE;
+
+ ngnfs_txn_init(&txn);
+ init_xattr_args(&xa, &ino, name, name_len, NULL, 0, NULL, 0);
+
+ do {
+ ret = ngnfs_inode_get(nfi, &txn, NBF_WRITE, ig, &ino) ?:
+ remove_xattr(nfi, &txn, &xa);
+
+ } while (ngnfs_txn_retry(nfi, &txn, &ret));
+
+ ngnfs_txn_teardown(nfi, &txn);
+
+ return ret;
+}
+
+static int insert_xattr_wr(struct ngnfs_btree_key *key, void *val, size_t val_size, void *arg,
+ struct ngnfs_btree_op *op)
+{
+ struct xattr_args *xa = arg;
+ struct ngnfs_xattr *xattr = val;
+
+ if (xattr) {
+ if (xattr_names_equal(xattr->name, xattr->name_len, (u8 *) xa->name, xa->name_len))
+ return -EEXIST;
+
+ if (xa->hash == le64_to_cpu(key->k[0])) {
+ if (xa->hash & NGNFS_DIRENT_COLL_BIT)
+ return -ENOSPC;
+ xa->hash |= NGNFS_DIRENT_COLL_BIT;
+ return NGNFS_BTREE_ITER_CONTINUE;
+ }
+ }
+
+ op->insert = 1;
+ op->val = xa->xattr;
+ op->val_size = xa->xa_size;
+ init_xattr_key(&op->key, xa->hash);
+
+ return 0;
+}
+
+static int insert_xattr(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn,
+ struct xattr_args *xa)
+{
+
+ struct ngnfs_btree_key key;
+ struct ngnfs_btree_key last;
+
+ init_xattr_key(&key, xa->hash);
+ init_xattr_key(&last, xa->hash | NGNFS_DIRENT_COLL_BIT);
+
+ return ngnfs_btree_write_iter(nfi, txn, xa->ino->tblk, &xa->ino->ninode->xattrs, &key,
+ &last, insert_xattr_wr, xa);
+}
+
+static int replace_xattr_wr(struct ngnfs_btree_key *key, void *val, size_t val_size, void *arg,
+ struct ngnfs_btree_op *op)
+{
+ struct xattr_args *xa = arg;
+ struct ngnfs_xattr *xattr = val;
+
+ if (!xattr)
+ return -ENODATA;
+
+ /* check for hash collision, retry once, then give up */
+ if (!xattr_names_equal(xattr->name, xattr->name_len, (u8 *) xa->name, xa->name_len)) {
+ if (xa->hash == le64_to_cpu(key->k[0])) {
+ if (xa->hash & NGNFS_DIRENT_COLL_BIT)
+ return -ENOSPC;
+ xa->hash |= NGNFS_DIRENT_COLL_BIT;
+ return NGNFS_BTREE_ITER_CONTINUE;
+ }
+ }
+
+ /* replace is done by setting both delete and insert */
+ op->delete = 1;
+ op->insert = 1;
+ op->val = xa->xattr;
+ op->val_size = xa->xa_size;
+ xa->found = 1;
+
+ return 0;
+}
+
+static int replace_xattr(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn,
+ struct xattr_args *xa)
+{
+
+ struct ngnfs_btree_key key;
+ struct ngnfs_btree_key last;
+ int ret;
+
+ init_xattr_key(&key, xa->hash);
+ init_xattr_key(&last, xa->hash | NGNFS_DIRENT_COLL_BIT);
+
+ ret = ngnfs_btree_write_iter(nfi, txn, xa->ino->tblk, &xa->ino->ninode->xattrs, &key,
+ &last, replace_xattr_wr, xa);
+
+ if (!xa->found)
+ ret = -ENODATA;
+
+ return ret;
+}
+
+/*
+ * Use the most efficient btree operation to implement setxattr
+ * depending on which flags are set. Replace means only set if it
+ * exists, create means only set if it does NOT exist, and no flags mean
+ * set it regardless of whether it exists.
+ */
+static int set_xattr(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn,
+ struct xattr_args *xa)
+{
+ int ret;
+
+ if (xa->flags & XATTR_REPLACE)
+ ret = replace_xattr(nfi, txn, xa);
+ else if (xa->flags & XATTR_CREATE)
+ ret = insert_xattr(nfi, txn, xa);
+ else {
+ ret = remove_xattr(nfi, txn, xa);
+ if (ret < 0 && ret != -ENODATA)
+ goto out;
+ ret = insert_xattr(nfi, txn, xa);
+ }
+out:
+ return ret;
+}
+
+int ngnfs_xattr_set(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *ig, char *name,
+ void *value, size_t val_size, int flags)
+{
+ struct ngnfs_transaction txn;
+ struct ngnfs_inode_txn_ref ino;
+ struct xattr_args xa;
+ struct ngnfs_xattr *xattr;
+ size_t xa_size;
+ size_t name_len;
+ int ret;
+
+ name_len = strlen(name);
+ if (name_len > XATTR_NAME_MAX)
+ return -ERANGE;
+
+ xa_size = xattr_size(name_len, val_size);
+ if (xa_size > NGNFS_XATTR_MAX_SIZE)
+ return -ERANGE;
+
+ xattr = kmalloc(xa_size, GFP_NOFS);
+ if (!xattr)
+ return -ENOMEM;
+
+ init_xattr_args(&xa, &ino, name, name_len, value, val_size, xattr, flags);
+
+ ngnfs_txn_init(&txn);
+
+ do {
+ ret = ngnfs_inode_get(nfi, &txn, NBF_WRITE, ig, &ino) ?:
+ set_xattr(nfi, &txn, &xa);
+
+ } while (ngnfs_txn_retry(nfi, &txn, &ret));
+
+ ngnfs_txn_teardown(nfi, &txn);
+
+ kfree(xattr);
+ return ret;
+}
diff --git a/shared/xattr.h b/shared/xattr.h
new file mode 100644
index 0000000..da3eeb4
--- /dev/null
+++ b/shared/xattr.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef NGNFS_SHARED_XATTR_H
+#define NGNFS_SHARED_XATTR_H
+
+#include "shared/fs_info.h"
+#include "shared/inode.h"
+
+ssize_t ngnfs_xattr_get(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *ig, char *name,
+ void *value, size_t val_size);
+int ngnfs_xattr_remove(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *ig, char *name);
+int ngnfs_xattr_set(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *ig, char *name,
+ void *value, size_t val_size, int flags);
+
+#endif
--
2.48.1
More information about the ngnfs-devel
mailing list