// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
 */

#include <linux/buffer_head.h>
#include <linux/xattr.h>
#include <linux/blk_types.h>
#include "apfs.h"

/**
 * apfs_xattr_from_query - Read the xattr record found by a successful query
 * @query:	the query that found the record
 * @xattr:	Return parameter.  The xattr record found.
 *
 * Reads the xattr record into @xattr and performs some basic sanity checks
 * as a protection against crafted filesystems.  Returns 0 on success or
 * -EFSCORRUPTED otherwise.
 *
 * The caller must not free @query while @xattr is in use, because @xattr->name
 * and @xattr->xdata point to data on disk.
 */
static int apfs_xattr_from_query(struct apfs_query *query,
				 struct apfs_xattr *xattr)
{
	struct super_block *sb = query->node->object.sb;
	struct apfs_xattr_val *xattr_val;
	struct apfs_xattr_key *xattr_key;
	char *raw = query->node->object.data;
	int datalen = query->len - sizeof(*xattr_val);
	int namelen = query->key_len - sizeof(*xattr_key);

	if (namelen < 1 || datalen < 0) {
		apfs_err(sb, "bad length of name (%d) or data (%d)", namelen, datalen);
		return -EFSCORRUPTED;
	}

	xattr_val = (struct apfs_xattr_val *)(raw + query->off);
	xattr_key = (struct apfs_xattr_key *)(raw + query->key_off);

	if (namelen != le16_to_cpu(xattr_key->name_len)) {
		apfs_err(sb, "inconsistent name length (%d vs %d)", namelen, le16_to_cpu(xattr_key->name_len));
		return -EFSCORRUPTED;
	}

	/* The xattr name must be NULL-terminated */
	if (xattr_key->name[namelen - 1] != 0) {
		apfs_err(sb, "null termination missing");
		return -EFSCORRUPTED;
	}

	xattr->has_dstream = le16_to_cpu(xattr_val->flags) &
			     APFS_XATTR_DATA_STREAM;

	if (xattr->has_dstream && datalen != sizeof(struct apfs_xattr_dstream)) {
		apfs_err(sb, "bad data length (%d)", datalen);
		return -EFSCORRUPTED;
	}
	if (!xattr->has_dstream && datalen != le16_to_cpu(xattr_val->xdata_len)) {
		apfs_err(sb, "inconsistent data length (%d vs %d)", datalen, le16_to_cpu(xattr_val->xdata_len));
		return -EFSCORRUPTED;
	}

	xattr->name = xattr_key->name;
	xattr->name_len = namelen - 1; /* Don't count the NULL termination */
	xattr->xdata = xattr_val->xdata;
	xattr->xdata_len = datalen;
	return 0;
}

/**
 * apfs_dstream_from_xattr - Get the dstream info for a dstream xattr
 * @sb:		filesystem superblock
 * @xattr:	in-memory xattr record (already sanity-checked)
 * @dstream:	on return, the data stream info
 */
static void apfs_dstream_from_xattr(struct super_block *sb, struct apfs_xattr *xattr, struct apfs_dstream_info *dstream)
{
	struct apfs_xattr_dstream *xdata = (void *)xattr->xdata;

	dstream->ds_sb = sb;
	dstream->ds_inode = NULL;
	dstream->ds_id = le64_to_cpu(xdata->xattr_obj_id);
	dstream->ds_size = le64_to_cpu(xdata->dstream.size);
	dstream->ds_sparse_bytes = 0; /* Irrelevant for xattrs */

	dstream->ds_cached_ext.len = 0;
	dstream->ds_ext_dirty = false;
	spin_lock_init(&dstream->ds_ext_lock);

	/* Xattrs can't be cloned */
	dstream->ds_shared = false;
}

/**
 * apfs_xattr_extents_read - Read the value of a xattr from its extents
 * @parent:	inode the attribute belongs to
 * @xattr:	the xattr structure
 * @buffer:	where to copy the attribute value
 * @size:	size of @buffer
 * @only_whole:	are partial reads banned?
 *
 * Copies the value of @xattr to @buffer, if provided. If @buffer is NULL, just
 * computes the size of the buffer required.
 *
 * Returns the number of bytes used/required, or a negative error code in case
 * of failure.
 */
static int apfs_xattr_extents_read(struct inode *parent,
				   struct apfs_xattr *xattr,
				   void *buffer, size_t size, bool only_whole)
{
	struct super_block *sb = parent->i_sb;
	struct apfs_dstream_info *dstream;
	int length, ret;

	dstream = kzalloc(sizeof(*dstream), GFP_KERNEL);
	if (!dstream)
		return -ENOMEM;
	apfs_dstream_from_xattr(sb, xattr, dstream);

	length = dstream->ds_size;
	if (length < 0 || length < dstream->ds_size) {
		/* TODO: avoid overflow here for huge compressed files */
		apfs_warn(sb, "xattr is too big to read on linux (0x%llx)", dstream->ds_size);
		ret = -E2BIG;
		goto out;
	}

	if (!buffer) {
		/* All we want is the length */
		ret = length;
		goto out;
	}

	if (only_whole) {
		if (length > size) {
			/* xattr won't fit in the buffer */
			ret = -ERANGE;
			goto out;
		}
	} else {
		if (length > size)
			length = size;
	}

	ret = apfs_nonsparse_dstream_read(dstream, buffer, length, 0 /* offset */);
	if (ret == 0)
		ret = length;

out:
	kfree(dstream);
	return ret;
}

/**
 * apfs_xattr_inline_read - Read the value of an inline xattr
 * @xattr:	the xattr structure
 * @buffer:	where to copy the attribute value
 * @size:	size of @buffer
 * @only_whole:	are partial reads banned?
 *
 * Copies the inline value of @xattr to @buffer, if provided. If @buffer is
 * NULL, just computes the size of the buffer required.
 *
 * Returns the number of bytes used/required, or a negative error code in case
 * of failure.
 */
static int apfs_xattr_inline_read(struct apfs_xattr *xattr, void *buffer, size_t size, bool only_whole)
{
	int length = xattr->xdata_len;

	if (!buffer) /* All we want is the length */
		return length;
	if (only_whole) {
		if (length > size) /* xattr won't fit in the buffer */
			return -ERANGE;
	} else {
		if (length > size)
			length = size;
	}
	memcpy(buffer, xattr->xdata, length);
	return length;
}

/**
 * apfs_xattr_get_compressed_data - Get the compressed data in a named attribute
 * @inode:	inode the attribute belongs to
 * @name:	name of the attribute
 * @cdata:	compressed data struct to set on return
 *
 * Returns 0 on success or a negative error code in case of failure.
 */
int apfs_xattr_get_compressed_data(struct inode *inode, const char *name, struct apfs_compressed_data *cdata)
{
	struct super_block *sb = inode->i_sb;
	struct apfs_sb_info *sbi = APFS_SB(sb);
	struct apfs_query *query;
	struct apfs_xattr xattr;
	u64 cnid = apfs_ino(inode);
	int ret;


	query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
	if (!query)
		return -ENOMEM;
	apfs_init_xattr_key(cnid, name, &query->key);
	query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;

	ret = apfs_btree_query(sb, &query);
	if (ret) {
		apfs_err(sb, "query failed for id 0x%llx (%s)", cnid, name);
		goto done;
	}

	ret = apfs_xattr_from_query(query, &xattr);
	if (ret) {
		apfs_err(sb, "bad xattr record in inode 0x%llx", cnid);
		goto done;
	}

	cdata->has_dstream = xattr.has_dstream;
	if (cdata->has_dstream) {
		struct apfs_dstream_info *dstream = NULL;

		dstream = kzalloc(sizeof(*dstream), GFP_KERNEL);
		if (!dstream) {
			ret = -ENOMEM;
			goto done;
		}
		apfs_dstream_from_xattr(sb, &xattr, dstream);

		cdata->dstream = dstream;
		cdata->size = dstream->ds_size;
	} else {
		void *data = NULL;
		int len;

		len = xattr.xdata_len;
		if (len > APFS_XATTR_MAX_EMBEDDED_SIZE) {
			apfs_err(sb, "inline xattr too big");
			ret = -EFSCORRUPTED;
			goto done;
		}
		data = kzalloc(len, GFP_KERNEL);
		if (!data) {
			ret = -ENOMEM;
			goto done;
		}
		memcpy(data, xattr.xdata, len);

		cdata->data = data;
		cdata->size = len;
	}
	ret = 0;

done:
	apfs_free_query(query);
	return ret;
}

/**
 * apfs_release_compressed_data - Clean up a compressed data struct
 * @cdata: struct to clean up (but not free)
 */
void apfs_release_compressed_data(struct apfs_compressed_data *cdata)
{
	if (!cdata)
		return;

	if (cdata->has_dstream) {
		kfree(cdata->dstream);
		cdata->dstream = NULL;
	} else {
		kfree(cdata->data);
		cdata->data = NULL;
	}
}

/**
 * apfs_compressed_data_read - Read from a compressed data struct
 * @cdata:	compressed data struct
 * @buf:	destination buffer
 * @count:	exact number of bytes to read
 * @offset:	dstream offset to read from
 *
 * Returns 0 on success or a negative error code in case of failure.
 */
int apfs_compressed_data_read(struct apfs_compressed_data *cdata, void *buf, size_t count, u64 offset)
{
	if (cdata->has_dstream)
		return apfs_nonsparse_dstream_read(cdata->dstream, buf, count, offset);

	if (offset > cdata->size || count > cdata->size - offset) {
		apfs_err(NULL, "reading past the end (0x%llx-0x%llx)", offset, (unsigned long long)count);
		/* No caller is expected to legitimately read out-of-bounds */
		return -EFSCORRUPTED;
	}
	memcpy(buf, cdata->data + offset, count);
	return 0;
}

/**
 * __apfs_xattr_get - Find and read a named attribute
 * @inode:	inode the attribute belongs to
 * @name:	name of the attribute
 * @buffer:	where to copy the attribute value
 * @size:	size of @buffer
 *
 * This does the same as apfs_xattr_get(), but without taking any locks.
 */
int __apfs_xattr_get(struct inode *inode, const char *name, void *buffer,
		     size_t size)
{
	return ____apfs_xattr_get(inode, name, buffer, size, true /* only_whole */);
}

/**
 * ____apfs_xattr_get - Find and read a named attribute, optionally header only
 * @inode:	inode the attribute belongs to
 * @name:	name of the attribute
 * @buffer:	where to copy the attribute value
 * @size:	size of @buffer
 * @only_whole:	must read complete (no partial header read allowed)
 */
int ____apfs_xattr_get(struct inode *inode, const char *name, void *buffer,
		       size_t size, bool only_whole)
{
	struct super_block *sb = inode->i_sb;
	struct apfs_sb_info *sbi = APFS_SB(sb);
	struct apfs_query *query;
	struct apfs_xattr xattr;
	u64 cnid = apfs_ino(inode);
	int ret;

	query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
	if (!query)
		return -ENOMEM;
	apfs_init_xattr_key(cnid, name, &query->key);
	query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;

	ret = apfs_btree_query(sb, &query);
	if (ret) {
		if (ret != -ENODATA)
			apfs_err(sb, "query failed for id 0x%llx (%s)", cnid, name);
		goto done;
	}

	ret = apfs_xattr_from_query(query, &xattr);
	if (ret) {
		apfs_err(sb, "bad xattr record in inode 0x%llx", cnid);
		goto done;
	}

	if (xattr.has_dstream)
		ret = apfs_xattr_extents_read(inode, &xattr, buffer, size, only_whole);
	else
		ret = apfs_xattr_inline_read(&xattr, buffer, size, only_whole);

done:
	apfs_free_query(query);
	return ret;
}

/**
 * apfs_xattr_get - Find and read a named attribute
 * @inode:	inode the attribute belongs to
 * @name:	name of the attribute
 * @buffer:	where to copy the attribute value
 * @size:	size of @buffer
 *
 * Finds an extended attribute and copies its value to @buffer, if provided. If
 * @buffer is NULL, just computes the size of the buffer required.
 *
 * Returns the number of bytes used/required, or a negative error code in case
 * of failure.
 */
static int apfs_xattr_get(struct inode *inode, const char *name, void *buffer, size_t size)
{
	struct apfs_nxsb_info *nxi = APFS_NXI(inode->i_sb);
	int ret;

	down_read(&nxi->nx_big_sem);
	ret = __apfs_xattr_get(inode, name, buffer, size);
	up_read(&nxi->nx_big_sem);
	if (ret > XATTR_SIZE_MAX) {
		apfs_warn(inode->i_sb, "xattr is too big to read on linux (%d)", ret);
		return -E2BIG;
	}
	return ret;
}

static int apfs_xattr_osx_get(const struct xattr_handler *handler,
				struct dentry *unused, struct inode *inode,
				const char *name, void *buffer, size_t size)
{
	/* Ignore the fake 'osx' prefix */
	return apfs_xattr_get(inode, name, buffer, size);
}

/**
 * apfs_delete_xattr - Delete an extended attribute
 * @query:	successful query pointing to the xattr to delete
 *
 * Returns 0 on success or a negative error code in case of failure.
 */
static int apfs_delete_xattr(struct apfs_query *query)
{
	struct super_block *sb = query->node->object.sb;
	struct apfs_xattr xattr;
	struct apfs_dstream_info *dstream;
	int err;

	err = apfs_xattr_from_query(query, &xattr);
	if (err) {
		apfs_err(sb, "bad xattr record");
		return err;
	}

	if (!xattr.has_dstream)
		return apfs_btree_remove(query);

	dstream = kzalloc(sizeof(*dstream), GFP_KERNEL);
	if (!dstream)
		return -ENOMEM;
	apfs_dstream_from_xattr(sb, &xattr, dstream);

	/*
	 * Remove the xattr record before truncation, because truncation creates
	 * new queries and makes ours invalid. This stuff is all too subtle, I
	 * really need to add some assertions (TODO).
	 */
	err = apfs_btree_remove(query);
	if (err) {
		apfs_err(sb, "removal failed");
		goto fail;
	}
	err = apfs_truncate(dstream, 0);
	if (err)
		apfs_err(sb, "truncation failed for dstream 0x%llx", dstream->ds_id);

fail:
	kfree(dstream);
	return err;
}

/**
 * apfs_delete_any_xattr - Delete any single xattr for a given inode
 * @inode: the vfs inode
 *
 * Intended to be called repeatedly, to delete all the xattrs one by one.
 * Returns -EAGAIN on success until the process is complete, then it returns
 * 0. Returns other negative error codes in case of failure.
 */
static int apfs_delete_any_xattr(struct inode *inode)
{
	struct super_block *sb = inode->i_sb;
	struct apfs_sb_info *sbi = APFS_SB(sb);
	struct apfs_query *query;
	int ret;

	query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
	if (!query)
		return -ENOMEM;
	apfs_init_xattr_key(apfs_ino(inode), NULL /* name */, &query->key);
	query->flags = APFS_QUERY_CAT | APFS_QUERY_ANY_NAME | APFS_QUERY_EXACT;

	ret = apfs_btree_query(sb, &query);
	if (ret) {
		if (ret == -ENODATA)
			ret = 0; /* No more xattrs, we are done */
		else
			apfs_err(sb, "query failed for ino 0x%llx", apfs_ino(inode));
		goto out;
	}

	ret = apfs_delete_xattr(query);
	if (!ret)
		ret = -EAGAIN;
	else
		apfs_err(sb, "xattr deletion failed");

out:
	apfs_free_query(query);
	return ret;
}

/**
 * apfs_delete_all_xattrs - Delete all xattrs for a given inode
 * @inode: the vfs inode
 *
 * Returns 0 on success or a negative error code in case of failure.
 */
int apfs_delete_all_xattrs(struct inode *inode)
{
	struct apfs_nxsb_info *nxi = APFS_NXI(inode->i_sb);
	int ret;

	lockdep_assert_held_write(&nxi->nx_big_sem);

	do {
		ret = apfs_delete_any_xattr(inode);
	} while (ret == -EAGAIN);

	return ret;
}

/**
 * apfs_build_xattr_key - Allocate and initialize the key for a xattr record
 * @name:	xattr name
 * @ino:	inode number for xattr's owner
 * @key_p:	on return, a pointer to the new on-disk key structure
 *
 * Returns the length of the key, or a negative error code in case of failure.
 */
static int apfs_build_xattr_key(const char *name, u64 ino, struct apfs_xattr_key **key_p)
{
	struct apfs_xattr_key *key;
	u16 namelen = strlen(name) + 1; /* We count the null-termination */
	int key_len;

	key_len = sizeof(*key) + namelen;
	key = kmalloc(key_len, GFP_KERNEL);
	if (!key)
		return -ENOMEM;

	apfs_key_set_hdr(APFS_TYPE_XATTR, ino, key);
	key->name_len = cpu_to_le16(namelen);
	strscpy(key->name, name, namelen);

	*key_p = key;
	return key_len;
}

/**
 * apfs_build_dstream_xattr_val - Allocate and init value for a dstream xattr
 * @dstream:	data stream info
 * @val_p:	on return, a pointer to the new on-disk value structure
 *
 * Returns the length of the value, or a negative error code in case of failure.
 */
static int apfs_build_dstream_xattr_val(struct apfs_dstream_info *dstream, struct apfs_xattr_val **val_p)
{
	struct apfs_xattr_val *val;
	struct apfs_xattr_dstream *dstream_raw;
	int val_len;

	val_len = sizeof(*val) + sizeof(*dstream_raw);
	val = kzalloc(val_len, GFP_KERNEL);
	if (!val)
		return -ENOMEM;

	val->flags = cpu_to_le16(APFS_XATTR_DATA_STREAM);
	val->xdata_len = cpu_to_le16(sizeof(*dstream_raw));

	dstream_raw = (void *)val->xdata;
	dstream_raw->xattr_obj_id = cpu_to_le64(dstream->ds_id);
	dstream_raw->dstream.size = cpu_to_le64(dstream->ds_size);
	dstream_raw->dstream.alloced_size = cpu_to_le64(apfs_alloced_size(dstream));
	if (apfs_vol_is_encrypted(dstream->ds_sb))
		dstream_raw->dstream.default_crypto_id = cpu_to_le64(dstream->ds_id);

	*val_p = val;
	return val_len;
}

/**
 * apfs_build_inline_xattr_val - Allocate and init value for an inline xattr
 * @value:	content of the xattr
 * @size:	size of @value
 * @val_p:	on return, a pointer to the new on-disk value structure
 *
 * Returns the length of the value, or a negative error code in case of failure.
 */
static int apfs_build_inline_xattr_val(const void *value, size_t size, struct apfs_xattr_val **val_p)
{
	struct apfs_xattr_val *val;
	int val_len;

	val_len = sizeof(*val) + size;
	val = kmalloc(val_len, GFP_KERNEL);
	if (!val)
		return -ENOMEM;

	val->flags = cpu_to_le16(APFS_XATTR_DATA_EMBEDDED);
	val->xdata_len = cpu_to_le16(size);
	memcpy(val->xdata, value, size);

	*val_p = val;
	return val_len;
}

/**
 * apfs_create_xattr_dstream - Create the extents for a dstream xattr
 * @sb:		filesystem superblock
 * @value:	value for the attribute
 * @size:	sizeo of @value
 *
 * Returns the info for the created data stream, or an error pointer in case
 * of failure.
 */
static struct apfs_dstream_info *apfs_create_xattr_dstream(struct super_block *sb, const void *value, size_t size)
{
	struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
	struct apfs_dstream_info *dstream;
	int blkcnt, i;
	int err;

	dstream = kzalloc(sizeof(*dstream), GFP_KERNEL);
	if (!dstream)
		return ERR_PTR(-ENOMEM);
	dstream->ds_sb = sb;
	spin_lock_init(&dstream->ds_ext_lock);

	apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
	dstream->ds_id = le64_to_cpu(vsb_raw->apfs_next_obj_id);
	le64_add_cpu(&vsb_raw->apfs_next_obj_id, 1);

	blkcnt = (size + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
	for (i = 0; i < blkcnt; i++) {
		struct buffer_head *bh;
		int off, tocopy;
		u64 bno;

		err = apfs_dstream_get_new_bno(dstream, i, &bno);
		if (err) {
			apfs_err(sb, "failed to get new block in dstream 0x%llx", dstream->ds_id);
			goto fail;
		}
		bh = apfs_sb_bread(sb, bno);
		if (!bh) {
			apfs_err(sb, "failed to read new block");
			err = -EIO;
			goto fail;
		}

		err = apfs_transaction_join(sb, bh);
		if (err) {
			brelse(bh);
			goto fail;
		}

		off = i << sb->s_blocksize_bits;
		tocopy = min(sb->s_blocksize, (unsigned long)(size - off));
		memcpy(bh->b_data, value + off, tocopy);
		if (tocopy < sb->s_blocksize)
			memset(bh->b_data + tocopy, 0, sb->s_blocksize - tocopy);
		brelse(bh);

		dstream->ds_size += tocopy;
	}

	err = apfs_flush_extent_cache(dstream);
	if (err) {
		apfs_err(sb, "extent cache flush failed for dstream 0x%llx", dstream->ds_id);
		goto fail;
	}
	return dstream;

fail:
	kfree(dstream);
	return ERR_PTR(err);
}

/**
 * apfs_xattr_dstream_from_query - Extract the dstream from a xattr record
 * @query:	the query that found the record
 * @dstream_p:	on return, the newly allocated dstream info (or NULL if none)
 *
 * Returns 0 on success, or a negative error code in case of failure.
 */
static int apfs_xattr_dstream_from_query(struct apfs_query *query, struct apfs_dstream_info **dstream_p)
{
	struct super_block *sb = query->node->object.sb;
	struct apfs_dstream_info *dstream = NULL;
	struct apfs_xattr xattr;
	int err;

	err = apfs_xattr_from_query(query, &xattr);
	if (err) {
		apfs_err(sb, "bad xattr record");
		return err;
	}

	if (xattr.has_dstream) {
		dstream = kzalloc(sizeof(*dstream), GFP_KERNEL);
		if (!dstream)
			return -ENOMEM;
		apfs_dstream_from_xattr(sb, &xattr, dstream);
	}
	*dstream_p = dstream;
	return 0;
}

/**
 * apfs_xattr_set - Write a named attribute
 * @inode:	inode the attribute will belong to
 * @name:	name for the attribute
 * @value:	value for the attribute
 * @size:	size of @value
 * @flags:	XATTR_REPLACE and XATTR_CREATE
 *
 * Returns 0 on success, or a negative error code in case of failure.
 */
int apfs_xattr_set(struct inode *inode, const char *name, const void *value,
		   size_t size, int flags)
{
	struct super_block *sb = inode->i_sb;
	struct apfs_sb_info *sbi = APFS_SB(sb);
	struct apfs_query *query = NULL;
	u64 cnid = apfs_ino(inode);
	int key_len, val_len;
	struct apfs_xattr_key *raw_key = NULL;
	struct apfs_xattr_val *raw_val = NULL;
	struct apfs_dstream_info *dstream = NULL;
	struct apfs_dstream_info *old_dstream = NULL;
	int ret;

	if (size > APFS_XATTR_MAX_EMBEDDED_SIZE) {
		dstream = apfs_create_xattr_dstream(sb, value, size);
		if (IS_ERR(dstream)) {
			apfs_err(sb, "failed to set xattr dstream for ino 0x%llx", apfs_ino(inode));
			return PTR_ERR(dstream);
		}
	}

	query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
	if (!query) {
		ret = -ENOMEM;
		goto done;
	}
	apfs_init_xattr_key(cnid, name, &query->key);
	query->flags = APFS_QUERY_CAT | APFS_QUERY_EXACT;

	ret = apfs_btree_query(sb, &query);
	if (ret) {
		if (ret != -ENODATA) {
			apfs_err(sb, "query failed for id 0x%llx (%s)", cnid, name);
			goto done;
		} else if (flags & XATTR_REPLACE) {
			goto done;
		}
	} else if (flags & XATTR_CREATE) {
		ret = -EEXIST;
		goto done;
	} else if (!value) {
		ret = apfs_delete_xattr(query);
		if (ret)
			apfs_err(sb, "xattr deletion failed");
		goto done;
	} else {
		/* Remember the old dstream to clean it up later */
		ret = apfs_xattr_dstream_from_query(query, &old_dstream);
		if (ret) {
			apfs_err(sb, "failed to get the old dstream");
			goto done;
		}
	}

	key_len = apfs_build_xattr_key(name, cnid, &raw_key);
	if (key_len < 0) {
		ret = key_len;
		goto done;
	}

	if (dstream)
		val_len = apfs_build_dstream_xattr_val(dstream, &raw_val);
	else
		val_len = apfs_build_inline_xattr_val(value, size, &raw_val);
	if (val_len < 0) {
		ret = val_len;
		goto done;
	}

	/* For now this is the only system xattr we support */
	if (strcmp(name, APFS_XATTR_NAME_SYMLINK) == 0)
		raw_val->flags |= cpu_to_le16(APFS_XATTR_FILE_SYSTEM_OWNED);

	if (ret)
		ret = apfs_btree_insert(query, raw_key, key_len, raw_val, val_len);
	else
		ret = apfs_btree_replace(query, raw_key, key_len, raw_val, val_len);
	if (ret) {
		apfs_err(sb, "insertion/update failed for id 0x%llx (%s)", cnid, name);
		goto done;
	}

	if (old_dstream) {
		ret = apfs_truncate(old_dstream, 0);
		if (ret)
			apfs_err(sb, "truncation failed for dstream 0x%llx", old_dstream->ds_id);
	}

done:
	kfree(dstream);
	kfree(old_dstream);
	kfree(raw_val);
	kfree(raw_key);
	apfs_free_query(query);
	return ret;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
static int apfs_xattr_osx_set(const struct xattr_handler *handler,
	      struct dentry *unused, struct inode *inode, const char *name,
	      const void *value, size_t size, int flags)
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0) && !RHEL_VERSION_GE(9, 6)
static int apfs_xattr_osx_set(const struct xattr_handler *handler,
		  struct user_namespace *mnt_userns, struct dentry *unused,
		  struct inode *inode, const char *name, const void *value,
		  size_t size, int flags)
#else
static int apfs_xattr_osx_set(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)
#endif
{
	struct super_block *sb = inode->i_sb;
	int err;

	err = apfs_transaction_start(sb, value ? APFS_TRANS_REG : APFS_TRANS_DEL);
	if (err)
		return err;

	/* Ignore the fake 'osx' prefix */
	err = apfs_xattr_set(inode, name, value, size, flags);
	if (err)
		goto fail;

	err = apfs_transaction_commit(sb);
	if (!err)
		return 0;

fail:
	apfs_transaction_abort(sb);
	return err;
}

static const struct xattr_handler apfs_xattr_osx_handler = {
	.prefix	= XATTR_MAC_OSX_PREFIX,
	.get	= apfs_xattr_osx_get,
	.set	= apfs_xattr_osx_set,
};

/* On-disk xattrs have no namespace; use a fake 'osx' prefix in the kernel */
const struct xattr_handler *apfs_xattr_handlers[] = {
	&apfs_xattr_osx_handler,
	NULL
};

ssize_t apfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
{
	struct inode *inode = d_inode(dentry);
	struct super_block *sb = inode->i_sb;
	struct apfs_sb_info *sbi = APFS_SB(sb);
	struct apfs_nxsb_info *nxi = APFS_NXI(sb);
	struct apfs_query *query;
	u64 cnid = apfs_ino(inode);
	size_t free = size;
	ssize_t ret;

	down_read(&nxi->nx_big_sem);

	query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
	if (!query) {
		ret = -ENOMEM;
		goto fail;
	}

	/* We want all the xattrs for the cnid, regardless of the name */
	apfs_init_xattr_key(cnid, NULL /* name */, &query->key);
	query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;

	while (1) {
		struct apfs_xattr xattr;

		ret = apfs_btree_query(sb, &query);
		if (ret == -ENODATA) { /* Got all the xattrs */
			ret = size - free;
			break;
		}
		if (ret) {
			apfs_err(sb, "query failed for id 0x%llx", cnid);
			break;
		}

		ret = apfs_xattr_from_query(query, &xattr);
		if (ret) {
			apfs_err(sb, "bad xattr key in inode %llx", cnid);
			break;
		}

		if (buffer) {
			/* Prepend the fake 'osx' prefix before listing */
			if (xattr.name_len + XATTR_MAC_OSX_PREFIX_LEN + 1 >
									free) {
				ret = -ERANGE;
				break;
			}
			memcpy(buffer, XATTR_MAC_OSX_PREFIX,
			       XATTR_MAC_OSX_PREFIX_LEN);
			buffer += XATTR_MAC_OSX_PREFIX_LEN;
			memcpy(buffer, xattr.name, xattr.name_len + 1);
			buffer += xattr.name_len + 1;
		}
		free -= xattr.name_len + XATTR_MAC_OSX_PREFIX_LEN + 1;
	}

fail:
	apfs_free_query(query);
	up_read(&nxi->nx_big_sem);
	return ret;
}
