blob: d07d325596d937dc613468a1cb3f61731d0492bc [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* erofs_utils/lib/xattr.c
*
* Originally contributed by an anonymous person,
* heavily changed by Li Guifu <blucerlee@gmail.com>
* and Gao Xiang <hsiangkao@aol.com>
*/
#include <stdlib.h>
#include <sys/xattr.h>
#ifdef HAVE_LINUX_XATTR_H
#include <linux/xattr.h>
#endif
#include "erofs/print.h"
#include "erofs/hashtable.h"
#include "erofs/xattr.h"
#define EA_HASHTABLE_BITS 16
struct xattr_item {
const char *kvbuf;
unsigned int hash[2], len[2], count;
u8 prefix;
struct hlist_node node;
};
struct inode_xattr_node {
struct list_head list;
struct xattr_item *item;
};
static DECLARE_HASHTABLE(ea_hashtable, EA_HASHTABLE_BITS);
static struct xattr_prefix {
const char *prefix;
u16 prefix_len;
} xattr_types[] = {
[EROFS_XATTR_INDEX_USER] = {
XATTR_USER_PREFIX,
XATTR_USER_PREFIX_LEN
}, [EROFS_XATTR_INDEX_POSIX_ACL_ACCESS] = {
XATTR_NAME_POSIX_ACL_ACCESS,
sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1
}, [EROFS_XATTR_INDEX_POSIX_ACL_DEFAULT] = {
XATTR_NAME_POSIX_ACL_DEFAULT,
sizeof(XATTR_NAME_POSIX_ACL_DEFAULT) - 1
}, [EROFS_XATTR_INDEX_TRUSTED] = {
XATTR_TRUSTED_PREFIX,
XATTR_TRUSTED_PREFIX_LEN
}, [EROFS_XATTR_INDEX_SECURITY] = {
XATTR_SECURITY_PREFIX,
XATTR_SECURITY_PREFIX_LEN
}
};
static unsigned int BKDRHash(char *str, unsigned int len)
{
const unsigned int seed = 131313;
unsigned int hash = 0;
while (len) {
hash = hash * seed + (*str++);
--len;
}
return hash;
}
static unsigned int xattr_item_hash(u8 prefix, char *buf,
unsigned int len[2], unsigned int hash[2])
{
hash[0] = BKDRHash(buf, len[0]); /* key */
hash[1] = BKDRHash(buf + len[0], len[1]); /* value */
return prefix ^ hash[0] ^ hash[1];
}
static unsigned int put_xattritem(struct xattr_item *item)
{
if (item->count > 1)
return --item->count;
free(item);
return 0;
}
static struct xattr_item *get_xattritem(u8 prefix, char *kvbuf,
unsigned int len[2])
{
struct xattr_item *item;
unsigned int hash[2], hkey;
hkey = xattr_item_hash(prefix, kvbuf, len, hash);
hash_for_each_possible(ea_hashtable, item, node, hkey) {
if (prefix == item->prefix &&
item->len[0] == len[0] && item->len[1] == len[1] &&
item->hash[0] == hash[0] && item->hash[1] == hash[1] &&
!memcmp(kvbuf, item->kvbuf, len[0] + len[1])) {
free(kvbuf);
++item->count;
return item;
}
}
item = malloc(sizeof(*item));
if (!item) {
free(kvbuf);
return ERR_PTR(-ENOMEM);
}
INIT_HLIST_NODE(&item->node);
item->count = 1;
item->kvbuf = kvbuf;
item->len[0] = len[0];
item->len[1] = len[1];
item->hash[0] = hash[0];
item->hash[1] = hash[1];
item->prefix = prefix;
hash_add(ea_hashtable, &item->node, hkey);
return item;
}
static bool match_prefix(const char *key, u8 *index, u16 *len)
{
struct xattr_prefix *p;
for (p = xattr_types; p < xattr_types + ARRAY_SIZE(xattr_types); ++p) {
if (p->prefix && !strncmp(p->prefix, key, p->prefix_len)) {
*len = p->prefix_len;
*index = p - xattr_types;
return true;
}
}
return false;
}
static struct xattr_item *parse_one_xattr(const char *path, const char *key,
unsigned int keylen)
{
ssize_t ret;
u8 prefix;
u16 prefixlen;
unsigned int len[2];
char *kvbuf;
erofs_dbg("parse xattr [%s] of %s", path, key);
if (!match_prefix(key, &prefix, &prefixlen))
return ERR_PTR(-ENODATA);
DBG_BUGON(keylen < prefixlen);
/* determine length of the value */
ret = lgetxattr(path, key, NULL, 0);
if (ret < 0)
return ERR_PTR(-errno);
len[1] = ret;
/* allocate key-value buffer */
len[0] = keylen - prefixlen;
kvbuf = malloc(len[0] + len[1]);
if (!kvbuf)
return ERR_PTR(-ENOMEM);
memcpy(kvbuf, key + prefixlen, len[0]);
if (len[1]) {
/* copy value to buffer */
ret = lgetxattr(path, key, kvbuf + len[0], len[1]);
if (ret < 0) {
free(kvbuf);
return ERR_PTR(-errno);
}
if (len[1] != ret) {
erofs_err("size of xattr value got changed just now (%u-> %ld)",
len[1], (long)ret);
len[1] = ret;
}
}
return get_xattritem(prefix, kvbuf, len);
}
static int inode_xattr_add(struct list_head *hlist, struct xattr_item *item)
{
struct inode_xattr_node *node = malloc(sizeof(*node));
if (!node)
return -ENOMEM;
init_list_head(&node->list);
node->item = item;
list_add(&node->list, hlist);
return 0;
}
static int read_xattrs_from_file(const char *path, struct list_head *ixattrs)
{
int ret = 0;
char *keylst, *key;
ssize_t kllen = llistxattr(path, NULL, 0);
if (kllen < 0 && errno != ENODATA) {
erofs_err("llistxattr to get the size of names for %s failed",
path);
return -errno;
}
if (kllen <= 1)
return 0;
keylst = malloc(kllen);
if (!keylst)
return -ENOMEM;
/* copy the list of attribute keys to the buffer.*/
kllen = llistxattr(path, keylst, kllen);
if (kllen < 0) {
erofs_err("llistxattr to get names for %s failed", path);
ret = -errno;
goto err;
}
/*
* loop over the list of zero terminated strings with the
* attribute keys. Use the remaining buffer length to determine
* the end of the list.
*/
key = keylst;
while (kllen > 0) {
unsigned int keylen = strlen(key);
struct xattr_item *item = parse_one_xattr(path, key, keylen);
if (IS_ERR(item)) {
ret = PTR_ERR(item);
goto err;
}
if (ixattrs) {
ret = inode_xattr_add(ixattrs, item);
if (ret < 0)
goto err;
}
kllen -= keylen + 1;
key += keylen + 1;
}
err:
free(keylst);
return ret;
}
int erofs_prepare_xattr_ibody(const char *path, struct list_head *ixattrs)
{
int ret;
struct inode_xattr_node *node;
/* check if xattr is disabled */
if (cfg.c_inline_xattr_tolerance < 0)
return 0;
ret = read_xattrs_from_file(path, ixattrs);
if (ret < 0)
return ret;
if (list_empty(ixattrs))
return 0;
/* get xattr ibody size */
ret = sizeof(struct erofs_xattr_ibody_header);
list_for_each_entry(node, ixattrs, list) {
const struct xattr_item *item = node->item;
ret += sizeof(struct erofs_xattr_entry);
ret = EROFS_XATTR_ALIGN(ret + item->len[0] + item->len[1]);
}
return ret;
}
char *erofs_export_xattr_ibody(struct list_head *ixattrs, unsigned int size)
{
struct inode_xattr_node *node, *n;
struct erofs_xattr_ibody_header *header;
unsigned int p;
char *buf = calloc(1, size);
if (!buf)
return ERR_PTR(-ENOMEM);
header = (struct erofs_xattr_ibody_header *)buf;
header->h_shared_count = 0;
p = sizeof(struct erofs_xattr_ibody_header);
list_for_each_entry_safe(node, n, ixattrs, list) {
struct xattr_item *const item = node->item;
const struct erofs_xattr_entry entry = {
.e_name_index = item->prefix,
.e_name_len = item->len[0],
.e_value_size = cpu_to_le16(item->len[1])
};
memcpy(buf + p, &entry, sizeof(entry));
p += sizeof(struct erofs_xattr_entry);
memcpy(buf + p, item->kvbuf, item->len[0] + item->len[1]);
p = EROFS_XATTR_ALIGN(p + item->len[0] + item->len[1]);
list_del(&node->list);
free(node);
put_xattritem(item);
}
DBG_BUGON(p > size);
return buf;
}