/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright (C) 2017-2018 HUAWEI, Inc.
 *             https://www.huawei.com/
 * Copyright (C) 2021-2022, Alibaba Cloud
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/dirent.h>
#include <sys/extattr.h>
#include <sys/kernel.h>
#include <sys/libkern.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/acl.h>

#include <machine/atomic.h>

#include "internal.h"
#include "xattr.h"

struct posix_acl_xattr_entry {
	uint16_t e_tag;
	uint16_t e_perm;
	uint32_t e_id;
};

struct posix_acl_xattr_header {
	uint32_t a_version;
};

#define POSIX_ACL_XATTR_VERSION 0x0002
#define EROFS_XATTR_FILTER_POSIX_ACL \
	((1U << 21) | (1U << 30))
#define EROFS_XATTR_CACHE_BODY_LIMIT (64UL * 1024)
#define EROFS_XATTR_CACHE_MOUNT_BUDGET (1024UL * 1024)

struct erofs_xattr_iter {
	struct erofs_sb_info *sbi;
	struct erofs_inode *vi;
	int attrnamespace;
	const char *name;
	size_t name_len;
	struct uio *uio;
	size_t *sizep;
};

static int erofs_xattr_load_body(struct erofs_sb_info *sbi,
    struct erofs_inode *vi, struct erofs_buf *bodybuf,
    struct erofs_xattr_ibody_header **ihp, size_t *header_sizep);

static uint32_t
erofs_xxh32_rotl(uint32_t value, unsigned int count)
{
	return ((value << count) | (value >> (32 - count)));
}

static uint32_t
erofs_xxh32_round(uint32_t seed, uint32_t input)
{
	seed += input * UINT32_C(2246822519);
	seed = erofs_xxh32_rotl(seed, 13);
	return (seed * UINT32_C(2654435761));
}

static uint32_t
erofs_xxh32(const void *input, size_t length, uint32_t seed)
{
	const uint8_t *cursor, *end;
	uint32_t hash;

	cursor = input;
	end = cursor + length;
	if (length >= 16) {
		const uint8_t *limit;
		uint32_t v1, v2, v3, v4;

		limit = end - 16;
		v1 = seed + UINT32_C(2654435761) + UINT32_C(2246822519);
		v2 = seed + UINT32_C(2246822519);
		v3 = seed;
		v4 = seed - UINT32_C(2654435761);
		do {
			v1 = erofs_xxh32_round(v1, le32dec(cursor));
			cursor += 4;
			v2 = erofs_xxh32_round(v2, le32dec(cursor));
			cursor += 4;
			v3 = erofs_xxh32_round(v3, le32dec(cursor));
			cursor += 4;
			v4 = erofs_xxh32_round(v4, le32dec(cursor));
			cursor += 4;
		} while (cursor <= limit);
		hash = erofs_xxh32_rotl(v1, 1) + erofs_xxh32_rotl(v2, 7) +
		    erofs_xxh32_rotl(v3, 12) + erofs_xxh32_rotl(v4, 18);
	} else {
		hash = seed + UINT32_C(374761393);
	}
	hash += (uint32_t)length;
	while (cursor + 4 <= end) {
		hash += le32dec(cursor) * UINT32_C(3266489917);
		hash = erofs_xxh32_rotl(hash, 17) * UINT32_C(668265263);
		cursor += 4;
	}
	while (cursor < end) {
		hash += *cursor++ * UINT32_C(374761393);
		hash = erofs_xxh32_rotl(hash, 11) * UINT32_C(2654435761);
	}
	hash ^= hash >> 15;
	hash *= UINT32_C(2246822519);
	hash ^= hash >> 13;
	hash *= UINT32_C(3266489917);
	hash ^= hash >> 16;
	return (hash);
}

static bool
erofs_xattr_filter_name(int attrnamespace, const char *name, size_t name_len,
    uint8_t *indexp, const char **filter_namep, size_t *filter_name_lenp)
{
	static const char acl_access[] = "posix_acl_access";
	static const char acl_default[] = "posix_acl_default";
	static const char security[] = "security.";
	static const char trusted[] = "trusted.";

	if (attrnamespace == EXTATTR_NAMESPACE_USER) {
		*indexp = EROFS_XATTR_INDEX_USER;
		*filter_namep = name;
		*filter_name_lenp = name_len;
		return (true);
	}
	if (attrnamespace != EXTATTR_NAMESPACE_SYSTEM)
		return (false);
	if (name_len == sizeof(acl_access) - 1 &&
	    memcmp(name, acl_access, sizeof(acl_access) - 1) == 0) {
		*indexp = EROFS_XATTR_INDEX_POSIX_ACL_ACCESS;
		*filter_namep = name + name_len;
		*filter_name_lenp = 0;
		return (true);
	}
	if (name_len == sizeof(acl_default) - 1 &&
	    memcmp(name, acl_default, sizeof(acl_default) - 1) == 0) {
		*indexp = EROFS_XATTR_INDEX_POSIX_ACL_DEFAULT;
		*filter_namep = name + name_len;
		*filter_name_lenp = 0;
		return (true);
	}
	if (name_len >= sizeof(trusted) - 1 &&
	    memcmp(name, trusted, sizeof(trusted) - 1) == 0) {
		*indexp = EROFS_XATTR_INDEX_TRUSTED;
		*filter_namep = name + sizeof(trusted) - 1;
		*filter_name_lenp = name_len - (sizeof(trusted) - 1);
		return (true);
	}
	if (name_len >= sizeof(security) - 1 &&
	    memcmp(name, security, sizeof(security) - 1) == 0) {
		*indexp = EROFS_XATTR_INDEX_SECURITY;
		*filter_namep = name + sizeof(security) - 1;
		*filter_name_lenp = name_len - (sizeof(security) - 1);
		return (true);
	}
	return (false);
}

static int
erofs_xattr_backing_size(struct erofs_sb_info *sbi, struct erofs_inode *backing_en,
    erofs_off_t *sizep)
{
	if (backing_en != NULL) {
		*sizep = backing_en->size;
		return (0);
	}
	if (sbi->blocks > (UINT64_MAX >> sbi->blkszbits))
		return (EOVERFLOW);
	*sizep = sbi->blocks << sbi->blkszbits;
	return (0);
}

static int
erofs_xattr_read_backing(struct erofs_sb_info *sbi,
    struct erofs_inode *backing_en, erofs_off_t off, size_t len,
    struct erofs_buf *buf)
{
	void *data;
	erofs_off_t backing_size;
	int error;

	error = erofs_xattr_backing_size(sbi, backing_en, &backing_size);
	if (error != 0)
		return (error);
	if (off > backing_size || (uint64_t)len > backing_size - off)
		return (EINTEGRITY);
	if (backing_en != NULL)
		error = erofs_read_data(sbi, backing_en, off, len, &data);
	else {
		if (off > INT64_MAX)
			return (EOVERFLOW);
		error = erofs_bread(sbi, (off_t)off, len, &data);
	}
	if (error != 0)
		return (error);
	buf->data = data;
	buf->release = erofs_brelse;
	return (0);
}

static int
erofs_xattr_filter_negative(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    int attrnamespace, const char *name, size_t name_len, bool *negativep)
{
	struct erofs_buf body = EROFS_BUF_INITIALIZER;
	struct erofs_xattr_ibody_header *ih;
	const char *filter_name;
	size_t filter_name_len, header_size;
	uint32_t hashbit, name_filter;
	uint8_t index;
	int error;

	*negativep = false;
	if (!erofs_sb_has_xattr_filter_v1(sbi) ||
	    !erofs_xattr_filter_name(attrnamespace, name, name_len, &index,
	    &filter_name, &filter_name_len))
		return (0);
	if (vi->xattr_isize < sizeof(*ih))
		return (EINTEGRITY);
	if (vi->xattr_isize == sizeof(*ih))
		return (EOPNOTSUPP);
	error = erofs_xattr_load_body(sbi, vi, &body, &ih, &header_size);
	if (error != 0)
		return (error);
	name_filter = le32toh(ih->h_name_filter);
	hashbit = erofs_xxh32(filter_name, filter_name_len,
	    EROFS_XATTR_FILTER_SEED + index);
	hashbit &= EROFS_XATTR_FILTER_BITS - 1;
	*negativep = (name_filter & (1U << hashbit)) != 0;
	error = 0;
	(void)header_size;
	erofs_put_metabuf(&body);
	return (error);
}

/*
 * Read one prefix table metadata record.
 *
 * When backing_en == NULL the record is in the physical metadata area;
 * otherwise it lives in the selected metadata carrier's logical data stream.
 */
static int
erofs_xattr_read_metadata(struct erofs_sb_info *sbi, struct erofs_inode *backing_en,
    erofs_off_t *offp, struct erofs_buf *buf, size_t *lenp)
{
	struct erofs_buf hdrbuf = EROFS_BUF_INITIALIZER;
	uint16_t raw_len;
	erofs_off_t off;
	size_t len;
	int error;

	if (*offp > UINT64_MAX - (sizeof(struct erofs_xattr_entry) - 1))
		return (EOVERFLOW);
	off = roundup2(*offp, sizeof(struct erofs_xattr_entry));
	error = erofs_xattr_read_backing(sbi, backing_en, off, sizeof(raw_len),
	    &hdrbuf);
	if (error != 0)
		return (error);
	raw_len = le16dec(hdrbuf.data);
	erofs_put_metabuf(&hdrbuf);
	len = (raw_len == 0) ? (size_t)UINT16_MAX + 1 : raw_len;
	if (len < sizeof(struct erofs_xattr_long_prefix) ||
	    len > EROFS_NAME_LEN + sizeof(struct erofs_xattr_long_prefix))
		return (EINTEGRITY);
	if (off > UINT64_MAX - sizeof(raw_len))
		return (EOVERFLOW);
	error = erofs_xattr_read_backing(sbi, backing_en,
	    off + sizeof(raw_len), len, buf);
	if (error != 0)
		return (error);
	*offp = off + sizeof(raw_len) + len;
	*lenp = len;
	return (0);
}

static int
erofs_xattr_move(void *value, size_t value_size, struct uio *uio, size_t *sizep)
{
	if (sizep != NULL)
		*sizep = value_size;
	if (uio == NULL || value_size == 0)
		return (0);
	return (uiomove(value, value_size, uio));
}

static int
erofs_xattr_load_body_uncached(struct erofs_sb_info *sbi,
    struct erofs_inode *vi,
    struct erofs_buf *bodybuf, struct erofs_xattr_ibody_header **ihp,
    size_t *header_sizep)
{
	struct erofs_buf buf = EROFS_BUF_INITIALIZER;
	struct erofs_xattr_ibody_header *ih;
	char *body;
	uint64_t body_off;
	size_t header_size;
	int error;

	if (vi->xattr_isize < sizeof(*ih))
		return (EINTEGRITY);
	if (vi->inode_off > UINT64_MAX - vi->inode_isize)
		return (EINTEGRITY);
	body_off = vi->inode_off + vi->inode_isize;
	error = erofs_xattr_read_backing(sbi,
	    erofs_nid_in_metabox(vi->nid) ? sbi->metabox_en : NULL, body_off,
	    vi->xattr_isize, &buf);
	if (error != 0)
		return (error);
	body = buf.data;
	ih = (struct erofs_xattr_ibody_header *)body;
	if (vi->xattr_isize == sizeof(*ih)) {
		error = EOPNOTSUPP;
		goto fail;
	}
	header_size = sizeof(*ih) + sizeof(uint32_t) * ih->h_shared_count;
	if (header_size > vi->xattr_isize) {
		error = EINTEGRITY;
		goto fail;
	}
	*bodybuf = buf;
	*ihp = ih;
	*header_sizep = header_size;
	return (0);
fail:
	erofs_put_metabuf(&buf);
	return (error);
}

static int
erofs_xattr_validate_entry(struct erofs_xattr_entry *entry, size_t remaining,
    size_t *entry_sizep, size_t *value_sizep)
{
	size_t entry_size, min_size, value_size;

	if (remaining < sizeof(*entry))
		return (EINTEGRITY);
	value_size = le16toh(entry->e_value_size);
	min_size = sizeof(*entry) + entry->e_name_len + value_size;
	if (min_size > remaining)
		return (EINTEGRITY);
	entry_size = erofs_xattr_entry_size(entry);
	if (entry_size > remaining)
		return (EINTEGRITY);
	if (memchr(entry->e_name, '\0', entry->e_name_len) != NULL)
		return (EINTEGRITY);
	if (entry_sizep != NULL)
		*entry_sizep = entry_size;
	if (value_sizep != NULL)
		*value_sizep = value_size;
	return (0);
}

static bool
erofs_xattr_prefix(uint8_t base_index, int *namespacep,
    const char **prefixp, size_t *prefix_lenp)
{
	switch (base_index) {
	case EROFS_XATTR_INDEX_USER:
		*namespacep = EXTATTR_NAMESPACE_USER;
		*prefixp = NULL;
		*prefix_lenp = 0;
		return (true);
	case EROFS_XATTR_INDEX_POSIX_ACL_ACCESS:
		*namespacep = EXTATTR_NAMESPACE_SYSTEM;
		*prefixp = "posix_acl_access";
		*prefix_lenp = sizeof("posix_acl_access") - 1;
		return (true);
	case EROFS_XATTR_INDEX_POSIX_ACL_DEFAULT:
		*namespacep = EXTATTR_NAMESPACE_SYSTEM;
		*prefixp = "posix_acl_default";
		*prefix_lenp = sizeof("posix_acl_default") - 1;
		return (true);
	case EROFS_XATTR_INDEX_TRUSTED:
		*namespacep = EXTATTR_NAMESPACE_SYSTEM;
		*prefixp = "trusted.";
		*prefix_lenp = sizeof("trusted.") - 1;
		return (true);
	case EROFS_XATTR_INDEX_SECURITY:
		*namespacep = EXTATTR_NAMESPACE_SYSTEM;
		*prefixp = "security.";
		*prefix_lenp = sizeof("security.") - 1;
		return (true);
	case EROFS_XATTR_INDEX_LUSTRE:
	default:
		return (false);
	}
}

static int
erofs_xattr_namespace_prefix(int attrnamespace, uint8_t base_index,
    const char **prefixp, size_t *prefix_lenp)
{
	int mapped_namespace;

	if (attrnamespace != EXTATTR_NAMESPACE_USER &&
	    attrnamespace != EXTATTR_NAMESPACE_SYSTEM)
		return (EOPNOTSUPP);
	if (!erofs_xattr_prefix(base_index, &mapped_namespace, prefixp,
	    prefix_lenp))
		return (ENOATTR);
	if (mapped_namespace != attrnamespace)
		return (ENOATTR);
	return (0);
}

static int
erofs_xattr_list_move(const char *namespace_prefix, size_t namespace_prefix_len,
    const char *infix, size_t infix_len, const char *name, uint8_t name_len,
    struct uio *uio, size_t *sizep)
{
	uint8_t total_name_len;
	int error;

	if (namespace_prefix_len + infix_len + name_len > EROFS_NAME_LEN)
		return (EINTEGRITY);
	total_name_len = namespace_prefix_len + infix_len + name_len;
	if (sizep != NULL) {
		*sizep += total_name_len + 1;
		return (0);
	}
	if (uio == NULL)
		return (0);
	error = uiomove(__DECONST(void *, &total_name_len), 1, uio);
	if (error != 0)
		return (error);
	if (namespace_prefix_len != 0) {
		error = uiomove(__DECONST(void *, namespace_prefix),
		    namespace_prefix_len, uio);
		if (error != 0)
			return (error);
	}
	if (infix_len != 0) {
		error = uiomove(__DECONST(void *, infix), infix_len, uio);
		if (error != 0)
			return (error);
	}
	return (uiomove(__DECONST(void *, name), name_len, uio));
}

static int
erofs_xattr_resolve_name(struct erofs_sb_info *sbi,
    const struct erofs_xattr_entry *entry, uint8_t *base_indexp,
    const char **infixp, size_t *infix_lenp)
{
	struct erofs_xattr_prefix_item *prefix;
	uint8_t prefix_id;

	if ((entry->e_name_index & EROFS_XATTR_LONG_PREFIX) == 0) {
		*base_indexp = entry->e_name_index;
		*infixp = NULL;
		*infix_lenp = 0;
		return (0);
	}
	if (sbi->xattr_prefixes == NULL)
		return (ENOATTR);
	prefix_id = entry->e_name_index & EROFS_XATTR_LONG_PREFIX_MASK;
	if (prefix_id >= sbi->xattr_prefix_count)
		return (ENOATTR);
	prefix = &sbi->xattr_prefixes[prefix_id];
	*base_indexp = prefix->base_index;
	*infixp = prefix->infix;
	*infix_lenp = prefix->infix_len;
	return (0);
}

static bool
erofs_xattr_name_match(const char *namespace_prefix,
    size_t namespace_prefix_len, const char *infix, size_t infix_len,
    const struct erofs_xattr_entry *entry, const char *name, size_t name_len)
{
	if (name_len != namespace_prefix_len + infix_len + entry->e_name_len)
		return (false);
	if (namespace_prefix_len != 0 &&
	    memcmp(name, namespace_prefix, namespace_prefix_len) != 0)
		return (false);
	if (infix_len != 0 &&
	    memcmp(name + namespace_prefix_len, infix, infix_len) != 0)
		return (false);
	return (memcmp(name + namespace_prefix_len + infix_len, entry->e_name,
		    entry->e_name_len) == 0);
}

static int
erofs_xattr_shared_entry_offset(struct erofs_sb_info *sbi, uint32_t shared_id,
    erofs_off_t *phys_offp)
{
	erofs_off_t base, relative;

	if (sbi->xattr_blkaddr > (UINT64_MAX >> sbi->blkszbits))
		return (EOVERFLOW);
	base = (uint64_t)sbi->xattr_blkaddr << sbi->blkszbits;
	relative = (uint64_t)shared_id * sizeof(uint32_t);
	if (relative > UINT64_MAX - base)
		return (EOVERFLOW);
	*phys_offp = base + relative;
	return (0);
}

static int
erofs_xattr_load_shared_entry(struct erofs_sb_info *sbi, uint32_t shared_id,
    struct erofs_buf *entrybuf, size_t *entry_sizep, size_t *value_sizep)
{
	struct erofs_buf hdrbuf = EROFS_BUF_INITIALIZER;
	struct erofs_xattr_entry *entry;
	struct erofs_inode *backing_en;
	erofs_off_t off;
	size_t entry_size, value_size;
	int error;

	backing_en = erofs_sb_has_shared_ea_in_metabox(sbi) ? sbi->metabox_en : NULL;
	if (erofs_sb_has_shared_ea_in_metabox(sbi) && backing_en == NULL)
		return (EINTEGRITY);

	error = erofs_xattr_shared_entry_offset(sbi, shared_id, &off);
	if (error != 0)
		return (error);

	error = erofs_xattr_read_backing(sbi, backing_en, off, sizeof(*entry),
	    &hdrbuf);
	if (error != 0)
		return (error);
	entry = hdrbuf.data;
	value_size = le16toh(entry->e_value_size);
	entry_size = erofs_xattr_entry_size(entry);

	erofs_put_metabuf(&hdrbuf);

	error = erofs_xattr_read_backing(sbi, backing_en, off, entry_size,
	    entrybuf);
	if (error != 0)
		return (error);
	error = erofs_xattr_validate_entry(entrybuf->data, entry_size, NULL,
	    value_sizep);
	if (error != 0) {
		erofs_put_metabuf(entrybuf);
		return (error);
	}
	if (entry_sizep != NULL)
		*entry_sizep = entry_size;
	if (value_sizep != NULL)
		*value_sizep = value_size;
	return (0);
}

static int
erofs_xattr_validate_body(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    void *body, struct erofs_xattr_ibody_header *ih, size_t header_size)
{
	struct erofs_buf entrybuf = EROFS_BUF_INITIALIZER;
	struct erofs_xattr_entry *entry;
	char *cursor;
	uint32_t shared_id;
	size_t entry_size, remaining;
	int error;

	if (header_size > vi->xattr_isize ||
	    memchr(ih->h_reserved2, '\0', sizeof(ih->h_reserved2)) == NULL)
		return (EINTEGRITY);
	remaining = vi->xattr_isize - header_size;
	cursor = (char *)body + header_size;
	while (remaining != 0) {
		entry = (struct erofs_xattr_entry *)cursor;
		error = erofs_xattr_validate_entry(entry, remaining, &entry_size,
		    NULL);
		if (error != 0)
			return (error);
		cursor += entry_size;
		remaining -= entry_size;
	}
	for (uint8_t i = 0; i < ih->h_shared_count; i++) {
		shared_id = le32toh(ih->h_shared_xattrs[i]);
		error = erofs_xattr_load_shared_entry(sbi, shared_id, &entrybuf,
		    NULL, NULL);
		if (error != 0)
			return (error);
		erofs_put_metabuf(&entrybuf);
	}
	return (0);
}

static int
erofs_xattr_load_body_checked(struct erofs_sb_info *sbi,
    struct erofs_inode *vi, struct erofs_buf *bodybuf,
    struct erofs_xattr_ibody_header **ihp, size_t *header_sizep)
{
	int error;

	error = erofs_xattr_load_body_uncached(sbi, vi, bodybuf, ihp,
	    header_sizep);
	if (error != 0)
		return (error);
	error = erofs_xattr_validate_body(sbi, vi, bodybuf->data, *ihp,
	    *header_sizep);
	if (error != 0)
		erofs_put_metabuf(bodybuf);
	return (error);
}

static bool
erofs_xattr_cache_reserve(struct erofs_sb_info *sbi, size_t size)
{
	u_long resident;

	for (;;) {
		resident = atomic_load_acq_long(&sbi->xattr_cache_resident);
		if (resident > EROFS_XATTR_CACHE_MOUNT_BUDGET ||
		    size > EROFS_XATTR_CACHE_MOUNT_BUDGET - resident)
			return (false);
		if (atomic_cmpset_acq_long(&sbi->xattr_cache_resident, resident,
		    resident + size))
			return (true);
	}
}

static void
erofs_xattr_cache_release(struct erofs_sb_info *sbi, size_t size)
{
	KASSERT(size <= EROFS_XATTR_CACHE_MOUNT_BUDGET,
	    ("erofs xattr cache release exceeds mount budget"));
	atomic_subtract_rel_long(&sbi->xattr_cache_resident, size);
}

static void *
erofs_xattr_cache_invalidate_locked(struct erofs_sb_info *sbi,
    struct erofs_xattr_cache *cache)
{
	void *data;

	data = cache->data;
	if (cache->charged_bytes != 0) {
		erofs_xattr_cache_release(sbi, cache->charged_bytes);
		cache->charged_bytes = 0;
	}
	cache->data = NULL;
	cache->size = 0;
	cache->error = 0;
	cache->state = EROFS_XATTR_CACHE_EMPTY;
	return (data);
}

void
erofs_xattr_cache_init(struct erofs_inode *vi)
{
	struct erofs_xattr_cache *cache;

	cache = &vi->xattr_cache;
	bzero(cache, sizeof(*cache));
	mtx_init(&cache->lock, "erofs xattr", NULL, MTX_DEF);
	cv_init(&cache->cv, "erofs xattr");
	cache->state = EROFS_XATTR_CACHE_EMPTY;
	cache->initialized = true;
}

void
erofs_xattr_cache_fini(struct erofs_sb_info *sbi, struct erofs_inode *vi)
{
	struct erofs_xattr_cache *cache;
	void *data;

	cache = &vi->xattr_cache;
	if (!cache->initialized)
		return;
	mtx_lock(&cache->lock);
	cache->closing = true;
	while (cache->state == EROFS_XATTR_CACHE_INFLIGHT ||
	    cache->waiters != 0)
		cv_wait(&cache->cv, &cache->lock);
	data = erofs_xattr_cache_invalidate_locked(sbi, cache);
	cache->initialized = false;
	mtx_unlock(&cache->lock);
	free(data, M_EROFS);
	cv_destroy(&cache->cv);
	mtx_destroy(&cache->lock);
}

static int
erofs_xattr_load_body(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    struct erofs_buf *bodybuf, struct erofs_xattr_ibody_header **ihp,
    size_t *header_sizep)
{
	struct erofs_xattr_cache *cache;
	bool reserved;
	int error;

	cache = &vi->xattr_cache;
	if (!cache->initialized || vi->xattr_isize > EROFS_XATTR_CACHE_BODY_LIMIT)
		return (erofs_xattr_load_body_checked(sbi, vi, bodybuf, ihp,
		    header_sizep));
	mtx_lock(&cache->lock);
	if (cache->closing) {
		mtx_unlock(&cache->lock);
		return (ENXIO);
	}
	if (cache->state == EROFS_XATTR_CACHE_READY) {
		KASSERT(cache->data != NULL,
		    ("erofs xattr ready cache has no body"));
		bodybuf->data = cache->data;
		*ihp = bodybuf->data;
		*header_sizep = sizeof(**ihp) +
		    sizeof(uint32_t) * (*ihp)->h_shared_count;
		mtx_unlock(&cache->lock);
		return (0);
	}
	if (cache->state == EROFS_XATTR_CACHE_INFLIGHT) {
		cache->waiters++;
		do {
			cv_wait(&cache->cv, &cache->lock);
		} while (cache->state == EROFS_XATTR_CACHE_INFLIGHT);
		if (cache->state == EROFS_XATTR_CACHE_READY) {
			bodybuf->data = cache->data;
			*ihp = bodybuf->data;
			*header_sizep = sizeof(**ihp) +
			    sizeof(uint32_t) * (*ihp)->h_shared_count;
			error = 0;
		} else {
			KASSERT(cache->state == EROFS_XATTR_CACHE_FAILED &&
			    cache->error > 0,
			    ("erofs xattr failed cache has no typed error"));
			error = cache->error;
		}
		cache->waiters--;
		if (cache->state == EROFS_XATTR_CACHE_FAILED &&
		    cache->waiters == 0)
			(void)erofs_xattr_cache_invalidate_locked(sbi, cache);
		if (cache->waiters == 0)
			cv_broadcast(&cache->cv);
		mtx_unlock(&cache->lock);
		return (error);
	}
	if (cache->state == EROFS_XATTR_CACHE_FAILED) {
		if (cache->waiters != 0) {
			mtx_unlock(&cache->lock);
			return (erofs_xattr_load_body_checked(sbi, vi, bodybuf, ihp,
			    header_sizep));
		}
		(void)erofs_xattr_cache_invalidate_locked(sbi, cache);
	}
	reserved = erofs_xattr_cache_reserve(sbi, vi->xattr_isize);
	if (!reserved) {
		mtx_unlock(&cache->lock);
		return (erofs_xattr_load_body_checked(sbi, vi, bodybuf, ihp,
		    header_sizep));
	}
	cache->state = EROFS_XATTR_CACHE_INFLIGHT;
	cache->error = 0;
	mtx_unlock(&cache->lock);

	error = erofs_xattr_load_body_checked(sbi, vi, bodybuf, ihp,
	    header_sizep);
	mtx_lock(&cache->lock);
	if (error == 0 && !cache->closing) {
		cache->data = bodybuf->data;
		cache->size = vi->xattr_isize;
		cache->charged_bytes = vi->xattr_isize;
		cache->state = EROFS_XATTR_CACHE_READY;
		bodybuf->release = NULL;
		cv_broadcast(&cache->cv);
		mtx_unlock(&cache->lock);
		return (0);
	}
	if (error == 0)
		error = ENXIO;
	erofs_xattr_cache_release(sbi, vi->xattr_isize);
	cache->error = error;
	cache->state = EROFS_XATTR_CACHE_FAILED;
	if (cache->waiters == 0)
		(void)erofs_xattr_cache_invalidate_locked(sbi, cache);
	cv_broadcast(&cache->cv);
	mtx_unlock(&cache->lock);
	erofs_put_metabuf(bodybuf);
	return (error);
}

static int
erofs_listxattr_foreach(struct erofs_xattr_iter *it,
    struct erofs_xattr_entry *entry)
{
	const char *infix, *namespace_prefix;
	size_t infix_len, namespace_prefix_len;
	uint8_t base_index;
	int error;

	error = erofs_xattr_resolve_name(it->sbi, entry, &base_index, &infix,
	    &infix_len);
	if (error == ENOATTR)
		return (0);
	if (error != 0)
		return (error);
	error = erofs_xattr_namespace_prefix(it->attrnamespace, base_index,
	    &namespace_prefix, &namespace_prefix_len);
	if (error == ENOATTR)
		return (0);
	if (error != 0)
		return (error);
	return (erofs_xattr_list_move(namespace_prefix, namespace_prefix_len,
	    infix, infix_len, entry->e_name, entry->e_name_len, it->uio,
	    it->sizep));
}

static int
erofs_getxattr_foreach(struct erofs_xattr_iter *it,
    struct erofs_xattr_entry *entry, size_t value_size)
{
	const char *infix, *namespace_prefix;
	size_t infix_len, namespace_prefix_len;
	uint8_t base_index;
	int error;

	error = erofs_xattr_resolve_name(it->sbi, entry, &base_index, &infix,
	    &infix_len);
	if (error != 0)
		return (error);
	error = erofs_xattr_namespace_prefix(it->attrnamespace, base_index,
	    &namespace_prefix, &namespace_prefix_len);
	if (error != 0)
		return (error);
	if (!erofs_xattr_name_match(namespace_prefix, namespace_prefix_len,
	    infix, infix_len, entry, it->name, it->name_len))
		return (ENOATTR);
	return (erofs_xattr_move(entry->e_name + entry->e_name_len, value_size,
	    it->uio, it->sizep));
}

static int
erofs_xattr_iter_inline(struct erofs_xattr_iter *it, char *body,
    size_t header_size, bool get)
{
	struct erofs_xattr_entry *entry;
	char *cursor;
	size_t entry_size, remaining, value_size;
	int error;

	remaining = it->vi->xattr_isize - header_size;
	cursor = body + header_size;
	while (remaining != 0) {
		entry = (struct erofs_xattr_entry *)cursor;
		error = erofs_xattr_validate_entry(entry, remaining,
		    &entry_size, get ? &value_size : NULL);
		if (error != 0)
			return (error);
		if (get)
			error = erofs_getxattr_foreach(it, entry, value_size);
		else
			error = erofs_listxattr_foreach(it, entry);
		if (get) {
			if (error != ENOATTR)
				return (error);
		} else if (error != 0) {
			return (error);
		}
		cursor += entry_size;
		remaining -= entry_size;
	}
	return (get ? ENOATTR : 0);
}

static int
erofs_xattr_iter_shared(struct erofs_xattr_iter *it,
    struct erofs_xattr_ibody_header *ih, bool get)
{
	struct erofs_buf buf = EROFS_BUF_INITIALIZER;
	struct erofs_xattr_entry *entry;
	uint32_t shared_id;
	size_t value_size;
	int error;

	for (uint8_t i = 0; i < ih->h_shared_count; i++) {
		shared_id = le32toh(ih->h_shared_xattrs[i]);
		error = erofs_xattr_load_shared_entry(it->sbi, shared_id, &buf,
		    NULL, get ? &value_size : NULL);
		if (error != 0)
			return (error);
		entry = buf.data;
		if (get)
			error = erofs_getxattr_foreach(it, entry, value_size);
		else
			error = erofs_listxattr_foreach(it, entry);
		erofs_put_metabuf(&buf);
		if (get) {
			if (error != ENOATTR)
				return (error);
		} else if (error != 0) {
			return (error);
		}
	}
	return (get ? ENOATTR : 0);
}

/*
 * Look up one inline/shared xattr by name.
 *
 * Name exposure rules:
 * - user namespace: bare name, no "user." prefix;
 * - system namespace: exposes full "trusted.*" / "security.*" names.
 */
int
erofs_getxattr(struct vnode *vp, int attrnamespace, const char *name,
    struct uio *uio, size_t *sizep)
{
	struct erofs_buf body = EROFS_BUF_INITIALIZER;
	struct erofs_sb_info *sbi;
	struct erofs_inode *vi;
	struct erofs_xattr_ibody_header *ih;
	struct erofs_xattr_iter it;
	size_t header_size, name_len;
	bool filter_negative;
	int error;

	sbi = MTOE(vp->v_mount);
	vi = VTOE(vp);
	if (name == NULL || name[0] == '\0')
		return (EINVAL);
	name_len = strlen(name);
	if (name_len > EROFS_NAME_LEN)
		return (EINVAL);
	if (vi->xattr_isize == 0)
		return (ENOATTR);
	error = erofs_xattr_filter_negative(sbi, vi, attrnamespace, name, name_len,
	    &filter_negative);
	if (error != 0)
		return (error);
	if (filter_negative)
		return (ENOATTR);
	error = erofs_xattr_load_body(sbi, vi, &body, &ih, &header_size);
	if (error != 0)
		return (error);
	it.sbi = sbi;
	it.vi = vi;
	it.attrnamespace = attrnamespace;
	it.name = name;
	it.name_len = name_len;
	it.uio = uio;
	it.sizep = sizep;
	error = erofs_xattr_iter_inline(&it, body.data, header_size, true);
	if (error == ENOATTR)
		error = erofs_xattr_iter_shared(&it, ih, true);
	erofs_put_metabuf(&body);
	return (error);
}

/*
 * Enumerate inline/shared xattr names for a given namespace.
 *
 * Return format: 1-byte name length followed by non-NUL-terminated name bytes.
 */
int
erofs_listxattr(struct vnode *vp, int attrnamespace, struct uio *uio,
    size_t *sizep)
{
	struct erofs_buf body = EROFS_BUF_INITIALIZER;
	struct erofs_sb_info *sbi;
	struct erofs_inode *vi;
	struct erofs_xattr_ibody_header *ih;
	struct erofs_xattr_iter it;
	size_t header_size;
	int error;

	sbi = MTOE(vp->v_mount);
	vi = VTOE(vp);
	if (sizep != NULL)
		*sizep = 0;
	if (vi->xattr_isize == 0)
		return (0);
	error = erofs_xattr_load_body(sbi, vi, &body, &ih, &header_size);
	if (error != 0)
		return (error);
	it.sbi = sbi;
	it.vi = vi;
	it.attrnamespace = attrnamespace;
	it.name = NULL;
	it.name_len = 0;
	it.uio = uio;
	it.sizep = sizep;
	error = erofs_xattr_iter_inline(&it, body.data, header_size, false);
	if (error == 0)
		error = erofs_xattr_iter_shared(&it, ih, false);
	erofs_put_metabuf(&body);
	return (error);
}

void
erofs_xattr_prefixes_cleanup(struct erofs_sb_info *sbi)
{
	if (sbi->xattr_prefixes == NULL)
		return;
	for (uint8_t i = 0; i < sbi->xattr_prefix_count; i++)
		free(sbi->xattr_prefixes[i].infix, M_EROFS);
	free(sbi->xattr_prefixes, M_EROFS);
	sbi->xattr_prefixes = NULL;
}

int
erofs_xattr_prefixes_init(struct erofs_sb_info *sbi)
{
	struct erofs_buf buf = EROFS_BUF_INITIALIZER;
	struct erofs_xattr_long_prefix *prefix = NULL;
	struct erofs_inode packed_en, *prefix_en;
	erofs_off_t off;
	size_t infix_len, len;
	int error;

	if (!erofs_sb_has_xattr_prefixes(sbi) || sbi->xattr_prefix_count == 0)
		return (0);
	prefix_en = NULL;
	if (!erofs_sb_has_plain_xattr_pfx(sbi)) {
		if (erofs_sb_has_metabox(sbi)) {
			if (sbi->metabox_en == NULL)
				return (EINTEGRITY);
			prefix_en = sbi->metabox_en;
		} else if (sbi->packed_inode != NULL) {
			prefix_en = sbi->packed_inode;
		} else if (sbi->packed_nid != 0) {
			error = erofs_read_inode(sbi, sbi->packed_nid, &packed_en);
			if (error != 0)
				return (error);
			if (packed_en.vtype != VREG)
				return (EINTEGRITY);
			prefix_en = &packed_en;
		}
	}
	sbi->xattr_prefixes = malloc(sizeof(*sbi->xattr_prefixes) *
		sbi->xattr_prefix_count,
	    M_EROFS, M_WAITOK | M_ZERO);
	off = (uint64_t)sbi->xattr_prefix_start << 2;
	for (uint8_t i = 0; i < sbi->xattr_prefix_count; i++) {
		error = erofs_xattr_read_metadata(sbi, prefix_en, &off,
		    &buf, &len);
		if (error != 0)
			goto fail;
		prefix = buf.data;
		infix_len = len - sizeof(*prefix);
		if (memchr(prefix->infix, '\0', infix_len) != NULL) {
			error = EINTEGRITY;
			goto fail;
		}
		sbi->xattr_prefixes[i].base_index = prefix->base_index;
		sbi->xattr_prefixes[i].infix_len = infix_len;
		sbi->xattr_prefixes[i].infix = malloc(infix_len + 1, M_EROFS,
		    M_WAITOK);
		memcpy(sbi->xattr_prefixes[i].infix, prefix->infix, infix_len);
		sbi->xattr_prefixes[i].infix[infix_len] = '\0';
		erofs_put_metabuf(&buf);
	}
	return (0);
fail:
	erofs_put_metabuf(&buf);
	erofs_xattr_prefixes_cleanup(sbi);
	return (error);
}

static int
erofs_inode_has_noacl(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    bool *noaclp)
{
	struct erofs_buf buf = EROFS_BUF_INITIALIZER;
	struct erofs_xattr_ibody_header *ih;
	struct erofs_inode *backing_en;
	erofs_off_t body_off;
	uint32_t name_filter;
	int error;

	*noaclp = false;
	if (vi->xattr_isize < sizeof(*ih)) {
		*noaclp = true;
		return (0);
	}
	if (!erofs_sb_has_xattr_filter(sbi) ||
	    sbi->xattr_filter_reserved != 0)
		return (0);
	if (vi->inode_off > UINT64_MAX - vi->inode_isize)
		return (EINTEGRITY);
	body_off = vi->inode_off + vi->inode_isize;
	backing_en = erofs_nid_in_metabox(vi->nid) ? sbi->metabox_en : NULL;
	error = erofs_xattr_read_backing(sbi, backing_en, body_off, sizeof(*ih),
	    &buf);
	if (error != 0)
		return (error);
	ih = buf.data;
	name_filter = le32toh(ih->h_name_filter);
	erofs_put_metabuf(&buf);
	*noaclp = (name_filter & EROFS_XATTR_FILTER_POSIX_ACL) ==
	    EROFS_XATTR_FILTER_POSIX_ACL;
	return (0);
}

static void
erofs_acl_from_mode(mode_t mode, acl_type_t type, struct acl *aclp)
{
	if (type == ACL_TYPE_DEFAULT) {
		aclp->acl_cnt = 0;
		return;
	}
	aclp->acl_cnt = 3;
	aclp->acl_entry[0].ae_tag = ACL_USER_OBJ;
	aclp->acl_entry[0].ae_id = ACL_UNDEFINED_ID;
	aclp->acl_entry[0].ae_perm = acl_posix1e_mode_to_perm(ACL_USER_OBJ, mode);
	aclp->acl_entry[1].ae_tag = ACL_GROUP_OBJ;
	aclp->acl_entry[1].ae_id = ACL_UNDEFINED_ID;
	aclp->acl_entry[1].ae_perm = acl_posix1e_mode_to_perm(ACL_GROUP_OBJ, mode);
	aclp->acl_entry[2].ae_tag = ACL_OTHER;
	aclp->acl_entry[2].ae_id = ACL_UNDEFINED_ID;
	aclp->acl_entry[2].ae_perm = acl_posix1e_mode_to_perm(ACL_OTHER, mode);
}

static int
erofs_posix_acl_from_xattr(const void *value, size_t size, mode_t mode,
    acl_type_t type, struct acl *aclp)
{
	const uint8_t *buf;
	struct posix_acl_xattr_header hdr;
	struct posix_acl_xattr_entry entry;
	uint32_t id;
	int count, i, j, phase;

	buf = value;
	if (size < sizeof(hdr) || (size - sizeof(hdr)) % sizeof(entry) != 0)
		return (EINTEGRITY);

	memcpy(&hdr, buf, sizeof(hdr));
	if (le32toh(hdr.a_version) != POSIX_ACL_XATTR_VERSION)
		return (EINTEGRITY);

	count = (size - sizeof(hdr)) / sizeof(entry);
	if (count > ACL_MAX_ENTRIES)
		return (EINTEGRITY);
	if (count == 0) {
		erofs_acl_from_mode(mode, type, aclp);
		return (0);
	}

	aclp->acl_cnt = count;
	phase = 0;
	for (i = 0; i < count; i++) {
		uint16_t tag, perm;

		memcpy(&entry, buf + sizeof(hdr) + i * sizeof(entry),
		    sizeof(entry));
		tag = le16toh(entry.e_tag);
		perm = le16toh(entry.e_perm);

		id = le32toh(entry.e_id);
		if ((perm & ~ACL_PERM_BITS) != 0)
			return (EINTEGRITY);
		switch (tag) {
		case ACL_USER_OBJ:
			if (phase != 0 || id != UINT32_MAX)
				return (EINTEGRITY);
			phase = 1;
			break;
		case ACL_USER:
			if ((phase != 1 && phase != 2) || id == UINT32_MAX)
				return (EINTEGRITY);
			phase = 2;
			break;
		case ACL_GROUP_OBJ:
			if ((phase != 1 && phase != 2) || id != UINT32_MAX)
				return (EINTEGRITY);
			phase = 3;
			break;
		case ACL_GROUP:
			if ((phase != 3 && phase != 4) || id == UINT32_MAX)
				return (EINTEGRITY);
			phase = 4;
			break;
		case ACL_MASK:
			if ((phase != 3 && phase != 4) || id != UINT32_MAX)
				return (EINTEGRITY);
			phase = 5;
			break;
		case ACL_OTHER:
			if ((phase != 3 && phase != 4 && phase != 5) ||
			    id != UINT32_MAX)
				return (EINTEGRITY);
			phase = 6;
			break;
		default:
			return (EINTEGRITY);
		}
		if (tag == ACL_USER || tag == ACL_GROUP) {
			for (j = 0; j < i; j++) {
				if (aclp->acl_entry[j].ae_tag == tag &&
				    aclp->acl_entry[j].ae_id == id)
					return (EINTEGRITY);
			}
		}

		aclp->acl_entry[i].ae_tag = tag;
		aclp->acl_entry[i].ae_perm = perm;
		aclp->acl_entry[i].ae_id = (id == UINT32_MAX) ? ACL_UNDEFINED_ID : id;
	}
	if (phase != 6 || acl_posix1e_check(aclp) != 0)
		return (EINTEGRITY);

	return (0);
}

int
erofs_get_acl(struct vnode *vp, acl_type_t type, struct acl *aclp)
{
	struct erofs_sb_info *sbi;
	struct erofs_inode *vi;
	const char *xattr_name;
	struct uio auio;
	struct iovec aiov;
	uint8_t buf[sizeof(struct posix_acl_xattr_header) +
	    sizeof(struct posix_acl_xattr_entry) * ACL_MAX_ENTRIES];
	size_t size;
	bool noacl;
	int error;

	sbi = MTOE(vp->v_mount);
	if (!test_opt(&sbi->opt, POSIX_ACL))
		return (EOPNOTSUPP);

	vi = VTOE(vp);

	switch (type) {
	case ACL_TYPE_ACCESS:
		xattr_name = "posix_acl_access";
		break;
	case ACL_TYPE_DEFAULT:
		if (vp->v_type != VDIR)
			return (EINVAL);
		xattr_name = "posix_acl_default";
		break;
	default:
		return (EINVAL);
	}
	error = erofs_inode_has_noacl(sbi, vi, &noacl);
	if (error != 0)
		return (error);
	if (noacl) {
		erofs_acl_from_mode(vi->mode, type, aclp);
		return (0);
	}

	error = erofs_getxattr(vp, EXTATTR_NAMESPACE_SYSTEM, xattr_name, NULL,
	    &size);
	if (error == ENOATTR) {
		erofs_acl_from_mode(vi->mode, type, aclp);
		return (0);
	}
	if (error != 0)
		return (error);
	if (size > sizeof(buf))
		return (EINTEGRITY);

	aiov.iov_base = buf;
	aiov.iov_len = size;
	auio.uio_iov = &aiov;
	auio.uio_iovcnt = 1;
	auio.uio_offset = 0;
	auio.uio_resid = size;
	auio.uio_segflg = UIO_SYSSPACE;
	auio.uio_rw = UIO_READ;
	auio.uio_td = curthread;
	error = erofs_getxattr(vp, EXTATTR_NAMESPACE_SYSTEM, xattr_name, &auio,
	    NULL);
	if (error != 0)
		return (error);
	if (auio.uio_resid != 0)
		return (EINTEGRITY);

	return (erofs_posix_acl_from_xattr(buf, size, vi->mode, type, aclp));
}
