summaryrefslogtreecommitdiff
path: root/lib/xattr.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/xattr.c')
-rw-r--r--lib/xattr.c309
1 files changed, 309 insertions, 0 deletions
diff --git a/lib/xattr.c b/lib/xattr.c
new file mode 100644
index 0000000..d07d325
--- /dev/null
+++ b/lib/xattr.c
@@ -0,0 +1,309 @@
+// 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;
+}
+