blob: bf63a81068d8a7a20191fa11c5f0d914c1d4b509 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
/*
* Originally contributed by an anonymous person,
* heavily changed by Li Guifu <blucerlee@gmail.com>
* and Gao Xiang <hsiangkao@aol.com>
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <sys/xattr.h>
#ifdef HAVE_LINUX_XATTR_H
#include <linux/xattr.h>
#endif
#include <sys/stat.h>
#include <dirent.h>
#include "erofs/print.h"
#include "erofs/hashtable.h"
#include "erofs/xattr.h"
#include "erofs/cache.h"
#include "erofs/io.h"
#include "erofs/fragments.h"
#include "erofs/xxhash.h"
#include "liberofs_private.h"
#ifndef XATTR_SYSTEM_PREFIX
#define XATTR_SYSTEM_PREFIX "system."
#endif
#ifndef XATTR_SYSTEM_PREFIX_LEN
#define XATTR_SYSTEM_PREFIX_LEN (sizeof(XATTR_SYSTEM_PREFIX) - 1)
#endif
#ifndef XATTR_USER_PREFIX
#define XATTR_USER_PREFIX "user."
#endif
#ifndef XATTR_USER_PREFIX_LEN
#define XATTR_USER_PREFIX_LEN (sizeof(XATTR_USER_PREFIX) - 1)
#endif
#ifndef XATTR_SECURITY_PREFIX
#define XATTR_SECURITY_PREFIX "security."
#endif
#ifndef XATTR_SECURITY_PREFIX_LEN
#define XATTR_SECURITY_PREFIX_LEN (sizeof(XATTR_SECURITY_PREFIX) - 1)
#endif
#ifndef XATTR_TRUSTED_PREFIX
#define XATTR_TRUSTED_PREFIX "trusted."
#endif
#ifndef XATTR_TRUSTED_PREFIX_LEN
#define XATTR_TRUSTED_PREFIX_LEN (sizeof(XATTR_TRUSTED_PREFIX) - 1)
#endif
#ifndef XATTR_NAME_POSIX_ACL_ACCESS
#define XATTR_NAME_POSIX_ACL_ACCESS "system.posix_acl_access"
#endif
#ifndef XATTR_NAME_POSIX_ACL_DEFAULT
#define XATTR_NAME_POSIX_ACL_DEFAULT "system.posix_acl_default"
#endif
#ifndef XATTR_NAME_SECURITY_SELINUX
#define XATTR_NAME_SECURITY_SELINUX "security.selinux"
#endif
#ifndef XATTR_NAME_SECURITY_CAPABILITY
#define XATTR_NAME_SECURITY_CAPABILITY "security.capability"
#endif
#ifndef OVL_XATTR_NAMESPACE
#define OVL_XATTR_NAMESPACE "overlay."
#endif
#ifndef OVL_XATTR_OPAQUE_POSTFIX
#define OVL_XATTR_OPAQUE_POSTFIX "opaque"
#endif
#ifndef OVL_XATTR_ORIGIN_POSTFIX
#define OVL_XATTR_ORIGIN_POSTFIX "origin"
#endif
#ifndef OVL_XATTR_TRUSTED_PREFIX
#define OVL_XATTR_TRUSTED_PREFIX XATTR_TRUSTED_PREFIX OVL_XATTR_NAMESPACE
#endif
#ifndef OVL_XATTR_OPAQUE
#define OVL_XATTR_OPAQUE OVL_XATTR_TRUSTED_PREFIX OVL_XATTR_OPAQUE_POSTFIX
#endif
#ifndef OVL_XATTR_ORIGIN
#define OVL_XATTR_ORIGIN OVL_XATTR_TRUSTED_PREFIX OVL_XATTR_ORIGIN_POSTFIX
#endif
#define EA_HASHTABLE_BITS 16
/* one extra byte for the trailing `\0` of attribute name */
#define EROFS_XATTR_KSIZE(kvlen) (kvlen[0] + 1)
#define EROFS_XATTR_KVSIZE(kvlen) (EROFS_XATTR_KSIZE(kvlen) + kvlen[1])
/*
* @base_index: the index of the matched predefined short prefix
* @prefix: the index of the matched long prefix, if any;
* same as base_index otherwise
* @prefix_len: the length of the matched long prefix if any;
* the length of the matched predefined short prefix otherwise
*/
struct xattr_item {
struct xattr_item *next_shared_xattr;
const char *kvbuf;
unsigned int hash[2], len[2], count;
int shared_xattr_id;
unsigned int prefix, base_index, prefix_len;
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_item *shared_xattrs_list;
static unsigned int shared_xattrs_count;
static struct xattr_prefix {
const char *prefix;
unsigned int 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
}
};
struct ea_type_node {
struct list_head list;
struct xattr_prefix type;
unsigned int index, base_index, base_len;
};
static LIST_HEAD(ea_name_prefixes);
static unsigned int ea_prefix_count;
static bool match_prefix(const char *key, unsigned int *index,
unsigned int *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 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(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 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(char *kvbuf, unsigned int len[2])
{
struct xattr_item *item;
struct ea_type_node *tnode;
unsigned int hash[2], hkey;
hkey = xattr_item_hash(kvbuf, len, hash);
hash_for_each_possible(ea_hashtable, item, node, hkey) {
if (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);
}
if (!match_prefix(kvbuf, &item->base_index, &item->prefix_len)) {
free(item);
free(kvbuf);
return ERR_PTR(-ENODATA);
}
DBG_BUGON(len[0] < item->prefix_len);
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->shared_xattr_id = -1;
item->prefix = item->base_index;
list_for_each_entry(tnode, &ea_name_prefixes, list) {
if (item->base_index == tnode->base_index &&
!strncmp(tnode->type.prefix, kvbuf,
tnode->type.prefix_len)) {
item->prefix = tnode->index;
item->prefix_len = tnode->type.prefix_len;
break;
}
}
hash_add(ea_hashtable, &item->node, hkey);
return item;
}
static struct xattr_item *parse_one_xattr(const char *path, const char *key,
unsigned int keylen)
{
ssize_t ret;
unsigned int len[2];
char *kvbuf;
erofs_dbg("parse xattr [%s] of %s", path, key);
/* length of the key */
len[0] = keylen;
/* determine length of the value */
#ifdef HAVE_LGETXATTR
ret = lgetxattr(path, key, NULL, 0);
#elif defined(__APPLE__)
ret = getxattr(path, key, NULL, 0, 0, XATTR_NOFOLLOW);
#else
return ERR_PTR(-EOPNOTSUPP);
#endif
if (ret < 0)
return ERR_PTR(-errno);
len[1] = ret;
/* allocate key-value buffer */
kvbuf = malloc(EROFS_XATTR_KVSIZE(len));
if (!kvbuf)
return ERR_PTR(-ENOMEM);
memcpy(kvbuf, key, EROFS_XATTR_KSIZE(len));
if (len[1]) {
/* copy value to buffer */
#ifdef HAVE_LGETXATTR
ret = lgetxattr(path, key, kvbuf + EROFS_XATTR_KSIZE(len),
len[1]);
#elif defined(__APPLE__)
ret = getxattr(path, key, kvbuf + EROFS_XATTR_KSIZE(len),
len[1], 0, XATTR_NOFOLLOW);
#else
free(kvbuf);
return ERR_PTR(-EOPNOTSUPP);
#endif
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(kvbuf, len);
}
static struct xattr_item *erofs_get_selabel_xattr(const char *srcpath,
mode_t mode)
{
#ifdef HAVE_LIBSELINUX
if (cfg.sehnd) {
char *secontext;
int ret;
unsigned int len[2];
char *kvbuf, *fspath;
if (cfg.mount_point)
ret = asprintf(&fspath, "/%s/%s", cfg.mount_point,
erofs_fspath(srcpath));
else
ret = asprintf(&fspath, "/%s", erofs_fspath(srcpath));
if (ret <= 0)
return ERR_PTR(-ENOMEM);
ret = selabel_lookup(cfg.sehnd, &secontext, fspath, mode);
free(fspath);
if (ret) {
ret = -errno;
if (ret != -ENOENT) {
erofs_err("failed to lookup selabel for %s: %s",
srcpath, erofs_strerror(ret));
return ERR_PTR(ret);
}
/* secontext = "u:object_r:unlabeled:s0"; */
return NULL;
}
len[0] = sizeof(XATTR_NAME_SECURITY_SELINUX) - 1;
len[1] = strlen(secontext);
kvbuf = malloc(EROFS_XATTR_KVSIZE(len));
if (!kvbuf) {
freecon(secontext);
return ERR_PTR(-ENOMEM);
}
sprintf(kvbuf, "%s", XATTR_NAME_SECURITY_SELINUX);
memcpy(kvbuf + EROFS_XATTR_KSIZE(len), secontext, len[1]);
freecon(secontext);
return get_xattritem(kvbuf, len);
}
#endif
return NULL;
}
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 shared_xattr_add(struct xattr_item *item)
{
item->next_shared_xattr = shared_xattrs_list;
shared_xattrs_list = item;
return ++shared_xattrs_count;
}
static int erofs_xattr_add(struct list_head *ixattrs, struct xattr_item *item)
{
if (ixattrs)
return inode_xattr_add(ixattrs, item);
if (item->count == cfg.c_inline_xattr_tolerance + 1) {
int ret = shared_xattr_add(item);
if (ret < 0)
return ret;
}
return 0;
}
static bool erofs_is_skipped_xattr(const char *key)
{
#ifdef HAVE_LIBSELINUX
/* if sehnd is valid, selabels will be overridden */
if (cfg.sehnd && !strcmp(key, XATTR_SECURITY_PREFIX "selinux"))
return true;
#endif
/* skip xattrs with unidentified "system." prefix */
if (!strncmp(key, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN)) {
if (!strcmp(key, XATTR_NAME_POSIX_ACL_ACCESS) ||
!strcmp(key, XATTR_NAME_POSIX_ACL_DEFAULT)) {
return false;
} else {
erofs_warn("skip unidentified xattr: %s", key);
return true;
}
}
return false;
}
static int read_xattrs_from_file(const char *path, mode_t mode,
struct list_head *ixattrs)
{
#ifdef HAVE_LLISTXATTR
ssize_t kllen = llistxattr(path, NULL, 0);
#elif defined(__APPLE__)
ssize_t kllen = listxattr(path, NULL, 0, XATTR_NOFOLLOW);
#else
ssize_t kllen = 0;
#endif
int ret;
char *keylst, *key, *klend;
unsigned int keylen;
struct xattr_item *item;
if (kllen < 0 && errno != ENODATA && errno != EOPNOTSUPP) {
erofs_err("llistxattr to get the size of names for %s failed",
path);
return -errno;
}
ret = 0;
if (kllen <= 1)
goto out;
keylst = malloc(kllen);
if (!keylst)
return -ENOMEM;
/* copy the list of attribute keys to the buffer.*/
#ifdef HAVE_LLISTXATTR
kllen = llistxattr(path, keylst, kllen);
#elif defined(__APPLE__)
kllen = listxattr(path, keylst, kllen, XATTR_NOFOLLOW);
if (kllen < 0) {
erofs_err("llistxattr to get names for %s failed", path);
ret = -errno;
goto err;
}
#else
ret = -EOPNOTSUPP;
goto err;
#endif
/*
* loop over the list of zero terminated strings with the
* attribute keys. Use the remaining buffer length to determine
* the end of the list.
*/
klend = keylst + kllen;
ret = 0;
for (key = keylst; key != klend; key += keylen + 1) {
keylen = strlen(key);
if (erofs_is_skipped_xattr(key))
continue;
item = parse_one_xattr(path, key, keylen);
if (IS_ERR(item)) {
ret = PTR_ERR(item);
goto err;
}
ret = erofs_xattr_add(ixattrs, item);
if (ret < 0)
goto err;
}
free(keylst);
out:
/* if some selabel is avilable, need to add right now */
item = erofs_get_selabel_xattr(path, mode);
if (IS_ERR(item))
return PTR_ERR(item);
if (item)
ret = erofs_xattr_add(ixattrs, item);
return ret;
err:
free(keylst);
return ret;
}
int erofs_setxattr(struct erofs_inode *inode, char *key,
const void *value, size_t size)
{
char *kvbuf;
unsigned int len[2];
struct xattr_item *item;
len[0] = strlen(key);
len[1] = size;
kvbuf = malloc(EROFS_XATTR_KVSIZE(len));
if (!kvbuf)
return -ENOMEM;
memcpy(kvbuf, key, EROFS_XATTR_KSIZE(len));
memcpy(kvbuf + EROFS_XATTR_KSIZE(len), value, size);
item = get_xattritem(kvbuf, len);
if (IS_ERR(item))
return PTR_ERR(item);
DBG_BUGON(!item);
return erofs_xattr_add(&inode->i_xattrs, item);
}
int erofs_set_opaque_xattr(struct erofs_inode *inode)
{
return erofs_setxattr(inode, OVL_XATTR_OPAQUE, "y", 1);
}
int erofs_set_origin_xattr(struct erofs_inode *inode)
{
return erofs_setxattr(inode, OVL_XATTR_ORIGIN, NULL, 0);
}
#ifdef WITH_ANDROID
static int erofs_droid_xattr_set_caps(struct erofs_inode *inode)
{
const u64 capabilities = inode->capabilities;
char *kvbuf;
unsigned int len[2];
struct vfs_cap_data caps;
struct xattr_item *item;
if (!capabilities)
return 0;
len[0] = sizeof(XATTR_NAME_SECURITY_CAPABILITY) - 1;
len[1] = sizeof(caps);
kvbuf = malloc(EROFS_XATTR_KVSIZE(len));
if (!kvbuf)
return -ENOMEM;
sprintf(kvbuf, "%s", XATTR_NAME_SECURITY_CAPABILITY);
caps.magic_etc = VFS_CAP_REVISION_2 | VFS_CAP_FLAGS_EFFECTIVE;
caps.data[0].permitted = (u32) capabilities;
caps.data[0].inheritable = 0;
caps.data[1].permitted = (u32) (capabilities >> 32);
caps.data[1].inheritable = 0;
memcpy(kvbuf + EROFS_XATTR_KSIZE(len), &caps, len[1]);
item = get_xattritem(kvbuf, len);
if (IS_ERR(item))
return PTR_ERR(item);
DBG_BUGON(!item);
return erofs_xattr_add(&inode->i_xattrs, item);
}
#else
static int erofs_droid_xattr_set_caps(struct erofs_inode *inode)
{
return 0;
}
#endif
int erofs_scan_file_xattrs(struct erofs_inode *inode)
{
int ret;
struct list_head *ixattrs = &inode->i_xattrs;
/* check if xattr is disabled */
if (cfg.c_inline_xattr_tolerance < 0)
return 0;
ret = read_xattrs_from_file(inode->i_srcpath, inode->i_mode, ixattrs);
if (ret < 0)
return ret;
return erofs_droid_xattr_set_caps(inode);
}
static inline unsigned int erofs_next_xattr_align(unsigned int pos,
struct xattr_item *item)
{
return EROFS_XATTR_ALIGN(pos + sizeof(struct erofs_xattr_entry) +
item->len[0] + item->len[1] - item->prefix_len);
}
int erofs_prepare_xattr_ibody(struct erofs_inode *inode)
{
int ret;
struct inode_xattr_node *node;
struct list_head *ixattrs = &inode->i_xattrs;
if (list_empty(ixattrs)) {
inode->xattr_isize = 0;
return 0;
}
/* get xattr ibody size */
ret = sizeof(struct erofs_xattr_ibody_header);
list_for_each_entry(node, ixattrs, list) {
struct xattr_item *item = node->item;
if (item->shared_xattr_id >= 0) {
ret += sizeof(__le32);
continue;
}
ret = erofs_next_xattr_align(ret, item);
}
inode->xattr_isize = ret;
return ret;
}
static int erofs_count_all_xattrs_from_path(const char *path)
{
int ret;
DIR *_dir;
struct stat st;
_dir = opendir(path);
if (!_dir) {
erofs_err("failed to opendir at %s: %s",
path, erofs_strerror(errno));
return -errno;
}
ret = 0;
while (1) {
struct dirent *dp;
char buf[PATH_MAX];
/*
* set errno to 0 before calling readdir() in order to
* distinguish end of stream and from an error.
*/
errno = 0;
dp = readdir(_dir);
if (!dp)
break;
if (is_dot_dotdot(dp->d_name) ||
!strncmp(dp->d_name, "lost+found", strlen("lost+found")))
continue;
ret = snprintf(buf, PATH_MAX, "%s/%s", path, dp->d_name);
if (ret < 0 || ret >= PATH_MAX) {
/* ignore the too long path */
ret = -ENOMEM;
goto fail;
}
ret = lstat(buf, &st);
if (ret) {
ret = -errno;
goto fail;
}
ret = read_xattrs_from_file(buf, st.st_mode, NULL);
if (ret)
goto fail;
if (!S_ISDIR(st.st_mode))
continue;
ret = erofs_count_all_xattrs_from_path(buf);
if (ret)
goto fail;
}
if (errno)
ret = -errno;
fail:
closedir(_dir);
return ret;
}
static void erofs_cleanxattrs(bool sharedxattrs)
{
unsigned int i;
struct xattr_item *item;
struct hlist_node *tmp;
hash_for_each_safe(ea_hashtable, i, tmp, item, node) {
if (sharedxattrs && item->shared_xattr_id >= 0)
continue;
hash_del(&item->node);
free(item);
}
if (sharedxattrs)
return;
shared_xattrs_count = 0;
}
static int comp_shared_xattr_item(const void *a, const void *b)
{
const struct xattr_item *ia, *ib;
unsigned int la, lb;
int ret;
ia = *((const struct xattr_item **)a);
ib = *((const struct xattr_item **)b);
la = ia->len[0] + ia->len[1];
lb = ib->len[0] + ib->len[1];
ret = strncmp(ia->kvbuf, ib->kvbuf, min(la, lb));
if (ret != 0)
return ret;
return la > lb;
}
int erofs_xattr_write_name_prefixes(struct erofs_sb_info *sbi, FILE *f)
{
struct ea_type_node *tnode;
off_t offset;
if (!ea_prefix_count)
return 0;
offset = ftello(f);
if (offset < 0)
return -errno;
if (offset > UINT32_MAX)
return -EOVERFLOW;
offset = round_up(offset, 4);
if (fseek(f, offset, SEEK_SET))
return -errno;
sbi->xattr_prefix_start = (u32)offset >> 2;
sbi->xattr_prefix_count = ea_prefix_count;
list_for_each_entry(tnode, &ea_name_prefixes, list) {
union {
struct {
__le16 size;
struct erofs_xattr_long_prefix prefix;
} s;
u8 data[EROFS_NAME_LEN + 2 +
sizeof(struct erofs_xattr_long_prefix)];
} u;
int len, infix_len;
u.s.prefix.base_index = tnode->base_index;
infix_len = tnode->type.prefix_len - tnode->base_len;
memcpy(u.s.prefix.infix, tnode->type.prefix + tnode->base_len,
infix_len);
len = sizeof(struct erofs_xattr_long_prefix) + infix_len;
u.s.size = cpu_to_le16(len);
if (fwrite(&u.s, sizeof(__le16) + len, 1, f) != 1)
return -EIO;
offset = round_up(offset + sizeof(__le16) + len, 4);
if (fseek(f, offset, SEEK_SET))
return -errno;
}
erofs_sb_set_fragments(sbi);
erofs_sb_set_xattr_prefixes(sbi);
return 0;
}
static void erofs_write_xattr_entry(char *buf, struct xattr_item *item)
{
struct erofs_xattr_entry entry = {
.e_name_index = item->prefix,
.e_name_len = item->len[0] - item->prefix_len,
.e_value_size = cpu_to_le16(item->len[1]),
};
memcpy(buf, &entry, sizeof(entry));
buf += sizeof(struct erofs_xattr_entry);
memcpy(buf, item->kvbuf + item->prefix_len,
item->len[0] - item->prefix_len);
buf += item->len[0] - item->prefix_len;
memcpy(buf, item->kvbuf + item->len[0] + 1, item->len[1]);
erofs_dbg("writing xattr %d %s (%d %s)", item->base_index, item->kvbuf,
item->prefix, item->kvbuf + item->prefix_len);
}
int erofs_build_shared_xattrs_from_path(struct erofs_sb_info *sbi, const char *path)
{
int ret;
struct erofs_buffer_head *bh;
struct xattr_item *item, *n, **sorted_n;
char *buf;
unsigned int p, i;
erofs_off_t off;
erofs_off_t shared_xattrs_size = 0;
/* check if xattr or shared xattr is disabled */
if (cfg.c_inline_xattr_tolerance < 0 ||
cfg.c_inline_xattr_tolerance == INT_MAX)
return 0;
if (shared_xattrs_count) {
DBG_BUGON(1);
return -EINVAL;
}
ret = erofs_count_all_xattrs_from_path(path);
if (ret)
return ret;
if (!shared_xattrs_count)
goto out;
sorted_n = malloc((shared_xattrs_count + 1) * sizeof(n));
if (!sorted_n)
return -ENOMEM;
i = 0;
while (shared_xattrs_list) {
item = shared_xattrs_list;
sorted_n[i++] = item;
shared_xattrs_list = item->next_shared_xattr;
shared_xattrs_size = erofs_next_xattr_align(shared_xattrs_size,
item);
}
DBG_BUGON(i != shared_xattrs_count);
sorted_n[i] = NULL;
qsort(sorted_n, shared_xattrs_count, sizeof(n), comp_shared_xattr_item);
buf = calloc(1, shared_xattrs_size);
if (!buf) {
free(sorted_n);
return -ENOMEM;
}
bh = erofs_balloc(XATTR, shared_xattrs_size, 0, 0);
if (IS_ERR(bh)) {
free(sorted_n);
free(buf);
return PTR_ERR(bh);
}
bh->op = &erofs_skip_write_bhops;
erofs_mapbh(bh->block);
off = erofs_btell(bh, false);
sbi->xattr_blkaddr = off / erofs_blksiz(sbi);
off %= erofs_blksiz(sbi);
p = 0;
for (i = 0; i < shared_xattrs_count; i++) {
item = sorted_n[i];
erofs_write_xattr_entry(buf + p, item);
item->next_shared_xattr = sorted_n[i + 1];
item->shared_xattr_id = (off + p) / sizeof(__le32);
p = erofs_next_xattr_align(p, item);
}
shared_xattrs_list = sorted_n[0];
free(sorted_n);
bh->op = &erofs_drop_directly_bhops;
ret = dev_write(sbi, buf, erofs_btell(bh, false), shared_xattrs_size);
free(buf);
erofs_bdrop(bh, false);
out:
erofs_cleanxattrs(true);
return ret;
}
char *erofs_export_xattr_ibody(struct erofs_inode *inode)
{
struct list_head *ixattrs = &inode->i_xattrs;
unsigned int size = inode->xattr_isize;
struct inode_xattr_node *node, *n;
struct xattr_item *item;
struct erofs_xattr_ibody_header *header;
LIST_HEAD(ilst);
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;
if (cfg.c_xattr_name_filter) {
u32 name_filter = 0;
int hashbit;
unsigned int base_len;
list_for_each_entry(node, ixattrs, list) {
item = node->item;
base_len = xattr_types[item->base_index].prefix_len;
hashbit = xxh32(item->kvbuf + base_len,
item->len[0] - base_len,
EROFS_XATTR_FILTER_SEED + item->base_index) &
(EROFS_XATTR_FILTER_BITS - 1);
name_filter |= (1UL << hashbit);
}
name_filter = EROFS_XATTR_FILTER_DEFAULT & ~name_filter;
header->h_name_filter = cpu_to_le32(name_filter);
if (header->h_name_filter)
erofs_sb_set_xattr_filter(inode->sbi);
}
p = sizeof(struct erofs_xattr_ibody_header);
list_for_each_entry_safe(node, n, ixattrs, list) {
item = node->item;
list_del(&node->list);
/* move inline xattrs to the onstack list */
if (item->shared_xattr_id < 0) {
list_add(&node->list, &ilst);
continue;
}
*(__le32 *)(buf + p) = cpu_to_le32(item->shared_xattr_id);
p += sizeof(__le32);
++header->h_shared_count;
free(node);
put_xattritem(item);
}
list_for_each_entry_safe(node, n, &ilst, list) {
item = node->item;
erofs_write_xattr_entry(buf + p, item);
p = erofs_next_xattr_align(p, item);
list_del(&node->list);
free(node);
put_xattritem(item);
}
DBG_BUGON(p > size);
return buf;
}
struct xattr_iter {
char page[EROFS_MAX_BLOCK_SIZE];
void *kaddr;
erofs_blk_t blkaddr;
unsigned int ofs;
struct erofs_sb_info *sbi;
};
static int init_inode_xattrs(struct erofs_inode *vi)
{
struct erofs_sb_info *sbi = vi->sbi;
struct xattr_iter it;
unsigned int i;
struct erofs_xattr_ibody_header *ih;
int ret = 0;
/* the most case is that xattrs of this inode are initialized. */
if (vi->flags & EROFS_I_EA_INITED)
return ret;
/*
* bypass all xattr operations if ->xattr_isize is not greater than
* sizeof(struct erofs_xattr_ibody_header), in detail:
* 1) it is not enough to contain erofs_xattr_ibody_header then
* ->xattr_isize should be 0 (it means no xattr);
* 2) it is just to contain erofs_xattr_ibody_header, which is on-disk
* undefined right now (maybe use later with some new sb feature).
*/
if (vi->xattr_isize == sizeof(struct erofs_xattr_ibody_header)) {
erofs_err("xattr_isize %d of nid %llu is not supported yet",
vi->xattr_isize, vi->nid);
return -EOPNOTSUPP;
} else if (vi->xattr_isize < sizeof(struct erofs_xattr_ibody_header)) {
if (vi->xattr_isize) {
erofs_err("bogus xattr ibody @ nid %llu", vi->nid);
DBG_BUGON(1);
return -EFSCORRUPTED; /* xattr ondisk layout error */
}
return -ENOATTR;
}
it.blkaddr = erofs_blknr(sbi, erofs_iloc(vi) + vi->inode_isize);
it.ofs = erofs_blkoff(sbi, erofs_iloc(vi) + vi->inode_isize);
ret = blk_read(sbi, 0, it.page, it.blkaddr, 1);
if (ret < 0)
return -EIO;
it.kaddr = it.page;
ih = (struct erofs_xattr_ibody_header *)(it.kaddr + it.ofs);
vi->xattr_shared_count = ih->h_shared_count;
vi->xattr_shared_xattrs = malloc(vi->xattr_shared_count * sizeof(uint));
if (!vi->xattr_shared_xattrs)
return -ENOMEM;
/* let's skip ibody header */
it.ofs += sizeof(struct erofs_xattr_ibody_header);
for (i = 0; i < vi->xattr_shared_count; ++i) {
if (it.ofs >= erofs_blksiz(sbi)) {
/* cannot be unaligned */
DBG_BUGON(it.ofs != erofs_blksiz(sbi));
ret = blk_read(sbi, 0, it.page, ++it.blkaddr, 1);
if (ret < 0) {
free(vi->xattr_shared_xattrs);
vi->xattr_shared_xattrs = NULL;
return -EIO;
}
it.kaddr = it.page;
it.ofs = 0;
}
vi->xattr_shared_xattrs[i] =
le32_to_cpu(*(__le32 *)(it.kaddr + it.ofs));
it.ofs += sizeof(__le32);
}
vi->flags |= EROFS_I_EA_INITED;
return ret;
}
/*
* the general idea for these return values is
* if 0 is returned, go on processing the current xattr;
* 1 (> 0) is returned, skip this round to process the next xattr;
* -err (< 0) is returned, an error (maybe ENOXATTR) occurred
* and need to be handled
*/
struct xattr_iter_handlers {
int (*entry)(struct xattr_iter *_it, struct erofs_xattr_entry *entry);
int (*name)(struct xattr_iter *_it, unsigned int processed, char *buf,
unsigned int len);
int (*alloc_buffer)(struct xattr_iter *_it, unsigned int value_sz);
void (*value)(struct xattr_iter *_it, unsigned int processed, char *buf,
unsigned int len);
};
static inline int xattr_iter_fixup(struct xattr_iter *it)
{
struct erofs_sb_info *sbi = it->sbi;
int ret;
if (it->ofs < erofs_blksiz(sbi))
return 0;
it->blkaddr += erofs_blknr(sbi, it->ofs);
ret = blk_read(sbi, 0, it->page, it->blkaddr, 1);
if (ret < 0)
return -EIO;
it->kaddr = it->page;
it->ofs = erofs_blkoff(sbi, it->ofs);
return 0;
}
static int inline_xattr_iter_pre(struct xattr_iter *it,
struct erofs_inode *vi)
{
struct erofs_sb_info *sbi = vi->sbi;
unsigned int xattr_header_sz, inline_xattr_ofs;
int ret;
xattr_header_sz = inlinexattr_header_size(vi);
if (xattr_header_sz >= vi->xattr_isize) {
DBG_BUGON(xattr_header_sz > vi->xattr_isize);
return -ENOATTR;
}
inline_xattr_ofs = vi->inode_isize + xattr_header_sz;
it->blkaddr = erofs_blknr(sbi, erofs_iloc(vi) + inline_xattr_ofs);
it->ofs = erofs_blkoff(sbi, erofs_iloc(vi) + inline_xattr_ofs);
ret = blk_read(sbi, 0, it->page, it->blkaddr, 1);
if (ret < 0)
return -EIO;
it->kaddr = it->page;
return vi->xattr_isize - xattr_header_sz;
}
/*
* Regardless of success or failure, `xattr_foreach' will end up with
* `ofs' pointing to the next xattr item rather than an arbitrary position.
*/
static int xattr_foreach(struct xattr_iter *it,
const struct xattr_iter_handlers *op,
unsigned int *tlimit)
{
struct erofs_sb_info *sbi = it->sbi;
struct erofs_xattr_entry entry;
unsigned int value_sz, processed, slice;
int err;
/* 0. fixup blkaddr, ofs, ipage */
err = xattr_iter_fixup(it);
if (err)
return err;
/*
* 1. read xattr entry to the memory,
* since we do EROFS_XATTR_ALIGN
* therefore entry should be in the page
*/
entry = *(struct erofs_xattr_entry *)(it->kaddr + it->ofs);
if (tlimit) {
unsigned int entry_sz = erofs_xattr_entry_size(&entry);
/* xattr on-disk corruption: xattr entry beyond xattr_isize */
if (*tlimit < entry_sz) {
DBG_BUGON(1);
return -EFSCORRUPTED;
}
*tlimit -= entry_sz;
}
it->ofs += sizeof(struct erofs_xattr_entry);
value_sz = le16_to_cpu(entry.e_value_size);
/* handle entry */
err = op->entry(it, &entry);
if (err) {
it->ofs += entry.e_name_len + value_sz;
goto out;
}
/* 2. handle xattr name (ofs will finally be at the end of name) */
processed = 0;
while (processed < entry.e_name_len) {
if (it->ofs >= erofs_blksiz(sbi)) {
DBG_BUGON(it->ofs > erofs_blksiz(sbi));
err = xattr_iter_fixup(it);
if (err)
goto out;
it->ofs = 0;
}
slice = min_t(unsigned int, erofs_blksiz(sbi) - it->ofs,
entry.e_name_len - processed);
/* handle name */
err = op->name(it, processed, it->kaddr + it->ofs, slice);
if (err) {
it->ofs += entry.e_name_len - processed + value_sz;
goto out;
}
it->ofs += slice;
processed += slice;
}
/* 3. handle xattr value */
processed = 0;
if (op->alloc_buffer) {
err = op->alloc_buffer(it, value_sz);
if (err) {
it->ofs += value_sz;
goto out;
}
}
while (processed < value_sz) {
if (it->ofs >= erofs_blksiz(sbi)) {
DBG_BUGON(it->ofs > erofs_blksiz(sbi));
err = xattr_iter_fixup(it);
if (err)
goto out;
it->ofs = 0;
}
slice = min_t(unsigned int, erofs_blksiz(sbi) - it->ofs,
value_sz - processed);
op->value(it, processed, it->kaddr + it->ofs, slice);
it->ofs += slice;
processed += slice;
}
out:
/* xattrs should be 4-byte aligned (on-disk constraint) */
it->ofs = EROFS_XATTR_ALIGN(it->ofs);
return err < 0 ? err : 0;
}
struct getxattr_iter {
struct xattr_iter it;
int buffer_size, index, infix_len;
char *buffer;
const char *name;
size_t len;
};
static int erofs_xattr_long_entrymatch(struct getxattr_iter *it,
struct erofs_xattr_entry *entry)
{
struct erofs_sb_info *sbi = it->it.sbi;
struct erofs_xattr_prefix_item *pf = sbi->xattr_prefixes +
(entry->e_name_index & EROFS_XATTR_LONG_PREFIX_MASK);
if (pf >= sbi->xattr_prefixes + sbi->xattr_prefix_count)
return -ENOATTR;
if (it->index != pf->prefix->base_index ||
it->len != entry->e_name_len + pf->infix_len)
return -ENOATTR;
if (memcmp(it->name, pf->prefix->infix, pf->infix_len))
return -ENOATTR;
it->infix_len = pf->infix_len;
return 0;
}
static int xattr_entrymatch(struct xattr_iter *_it,
struct erofs_xattr_entry *entry)
{
struct getxattr_iter *it = container_of(_it, struct getxattr_iter, it);
/* should also match the infix for long name prefixes */
if (entry->e_name_index & EROFS_XATTR_LONG_PREFIX)
return erofs_xattr_long_entrymatch(it, entry);
if (it->index != entry->e_name_index ||
it->len != entry->e_name_len)
return -ENOATTR;
it->infix_len = 0;
return 0;
}
static int xattr_namematch(struct xattr_iter *_it,
unsigned int processed, char *buf, unsigned int len)
{
struct getxattr_iter *it = container_of(_it, struct getxattr_iter, it);
if (memcmp(buf, it->name + it->infix_len + processed, len))
return -ENOATTR;
return 0;
}
static int xattr_checkbuffer(struct xattr_iter *_it,
unsigned int value_sz)
{
struct getxattr_iter *it = container_of(_it, struct getxattr_iter, it);
int err = it->buffer_size < value_sz ? -ERANGE : 0;
it->buffer_size = value_sz;
return !it->buffer ? 1 : err;
}
static void xattr_copyvalue(struct xattr_iter *_it,
unsigned int processed,
char *buf, unsigned int len)
{
struct getxattr_iter *it = container_of(_it, struct getxattr_iter, it);
memcpy(it->buffer + processed, buf, len);
}
static const struct xattr_iter_handlers find_xattr_handlers = {
.entry = xattr_entrymatch,
.name = xattr_namematch,
.alloc_buffer = xattr_checkbuffer,
.value = xattr_copyvalue
};
static int inline_getxattr(struct erofs_inode *vi, struct getxattr_iter *it)
{
int ret;
unsigned int remaining;
ret = inline_xattr_iter_pre(&it->it, vi);
if (ret < 0)
return ret;
remaining = ret;
while (remaining) {
ret = xattr_foreach(&it->it, &find_xattr_handlers, &remaining);
if (ret != -ENOATTR)
break;
}
return ret ? ret : it->buffer_size;
}
static int shared_getxattr(struct erofs_inode *vi, struct getxattr_iter *it)
{
unsigned int i;
int ret = -ENOATTR;
for (i = 0; i < vi->xattr_shared_count; ++i) {
erofs_blk_t blkaddr =
xattrblock_addr(vi, vi->xattr_shared_xattrs[i]);
it->it.ofs = xattrblock_offset(vi, vi->xattr_shared_xattrs[i]);
if (!i || blkaddr != it->it.blkaddr) {
ret = blk_read(vi->sbi, 0, it->it.page, blkaddr, 1);
if (ret < 0)
return -EIO;
it->it.kaddr = it->it.page;
it->it.blkaddr = blkaddr;
}
ret = xattr_foreach(&it->it, &find_xattr_handlers, NULL);
if (ret != -ENOATTR)
break;
}
return ret ? ret : it->buffer_size;
}
int erofs_getxattr(struct erofs_inode *vi, const char *name, char *buffer,
size_t buffer_size)
{
int ret;
unsigned int prefix, prefixlen;
struct getxattr_iter it;
if (!name)
return -EINVAL;
ret = init_inode_xattrs(vi);
if (ret)
return ret;
if (!match_prefix(name, &prefix, &prefixlen))
return -ENODATA;
it.it.sbi = vi->sbi;
it.index = prefix;
it.name = name + prefixlen;
it.len = strlen(it.name);
if (it.len > EROFS_NAME_LEN)
return -ERANGE;
it.buffer = buffer;
it.buffer_size = buffer_size;
ret = inline_getxattr(vi, &it);
if (ret == -ENOATTR)
ret = shared_getxattr(vi, &it);
return ret;
}
struct listxattr_iter {
struct xattr_iter it;
char *buffer;
int buffer_size, buffer_ofs;
};
static int xattr_entrylist(struct xattr_iter *_it,
struct erofs_xattr_entry *entry)
{
struct listxattr_iter *it =
container_of(_it, struct listxattr_iter, it);
unsigned int base_index = entry->e_name_index;
unsigned int prefix_len, infix_len = 0;
const char *prefix, *infix = NULL;
if (entry->e_name_index & EROFS_XATTR_LONG_PREFIX) {
struct erofs_sb_info *sbi = _it->sbi;
struct erofs_xattr_prefix_item *pf = sbi->xattr_prefixes +
(entry->e_name_index & EROFS_XATTR_LONG_PREFIX_MASK);
if (pf >= sbi->xattr_prefixes + sbi->xattr_prefix_count)
return 1;
infix = pf->prefix->infix;
infix_len = pf->infix_len;
base_index = pf->prefix->base_index;
}
if (base_index >= ARRAY_SIZE(xattr_types))
return 1;
prefix = xattr_types[base_index].prefix;
prefix_len = xattr_types[base_index].prefix_len;
if (!it->buffer) {
it->buffer_ofs += prefix_len + infix_len +
entry->e_name_len + 1;
return 1;
}
if (it->buffer_ofs + prefix_len + infix_len
+ entry->e_name_len + 1 > it->buffer_size)
return -ERANGE;
memcpy(it->buffer + it->buffer_ofs, prefix, prefix_len);
memcpy(it->buffer + it->buffer_ofs + prefix_len, infix, infix_len);
it->buffer_ofs += prefix_len + infix_len;
return 0;
}
static int xattr_namelist(struct xattr_iter *_it,
unsigned int processed, char *buf, unsigned int len)
{
struct listxattr_iter *it =
container_of(_it, struct listxattr_iter, it);
memcpy(it->buffer + it->buffer_ofs, buf, len);
it->buffer_ofs += len;
return 0;
}
static int xattr_skipvalue(struct xattr_iter *_it,
unsigned int value_sz)
{
struct listxattr_iter *it =
container_of(_it, struct listxattr_iter, it);
it->buffer[it->buffer_ofs++] = '\0';
return 1;
}
static const struct xattr_iter_handlers list_xattr_handlers = {
.entry = xattr_entrylist,
.name = xattr_namelist,
.alloc_buffer = xattr_skipvalue,
.value = NULL
};
static int inline_listxattr(struct erofs_inode *vi, struct listxattr_iter *it)
{
int ret;
unsigned int remaining;
ret = inline_xattr_iter_pre(&it->it, vi);
if (ret < 0)
return ret;
remaining = ret;
while (remaining) {
ret = xattr_foreach(&it->it, &list_xattr_handlers, &remaining);
if (ret)
break;
}
return ret ? ret : it->buffer_ofs;
}
static int shared_listxattr(struct erofs_inode *vi, struct listxattr_iter *it)
{
unsigned int i;
int ret = 0;
for (i = 0; i < vi->xattr_shared_count; ++i) {
erofs_blk_t blkaddr =
xattrblock_addr(vi, vi->xattr_shared_xattrs[i]);
it->it.ofs = xattrblock_offset(vi, vi->xattr_shared_xattrs[i]);
if (!i || blkaddr != it->it.blkaddr) {
ret = blk_read(vi->sbi, 0, it->it.page, blkaddr, 1);
if (ret < 0)
return -EIO;
it->it.kaddr = it->it.page;
it->it.blkaddr = blkaddr;
}
ret = xattr_foreach(&it->it, &list_xattr_handlers, NULL);
if (ret)
break;
}
return ret ? ret : it->buffer_ofs;
}
int erofs_listxattr(struct erofs_inode *vi, char *buffer, size_t buffer_size)
{
int ret;
struct listxattr_iter it;
ret = init_inode_xattrs(vi);
if (ret == -ENOATTR)
return 0;
if (ret)
return ret;
it.it.sbi = vi->sbi;
it.buffer = buffer;
it.buffer_size = buffer_size;
it.buffer_ofs = 0;
ret = inline_listxattr(vi, &it);
if (ret < 0 && ret != -ENOATTR)
return ret;
return shared_listxattr(vi, &it);
}
int erofs_xattr_insert_name_prefix(const char *prefix)
{
struct ea_type_node *tnode;
if (ea_prefix_count >= 0x80 || strlen(prefix) > UINT8_MAX)
return -EOVERFLOW;
tnode = calloc(1, sizeof(*tnode));
if (!tnode)
return -ENOMEM;
if (!match_prefix(prefix, &tnode->base_index, &tnode->base_len)) {
free(tnode);
return -ENODATA;
}
tnode->type.prefix_len = strlen(prefix);
tnode->type.prefix = strdup(prefix);
if (!tnode->type.prefix) {
free(tnode);
return -ENOMEM;
}
tnode->index = EROFS_XATTR_LONG_PREFIX | ea_prefix_count;
ea_prefix_count++;
init_list_head(&tnode->list);
list_add_tail(&tnode->list, &ea_name_prefixes);
return 0;
}
void erofs_xattr_cleanup_name_prefixes(void)
{
struct ea_type_node *tnode, *n;
list_for_each_entry_safe(tnode, n, &ea_name_prefixes, list) {
list_del(&tnode->list);
free((void *)tnode->type.prefix);
free(tnode);
}
}
void erofs_xattr_prefixes_cleanup(struct erofs_sb_info *sbi)
{
int i;
if (sbi->xattr_prefixes) {
for (i = 0; i < sbi->xattr_prefix_count; i++)
free(sbi->xattr_prefixes[i].prefix);
free(sbi->xattr_prefixes);
sbi->xattr_prefixes = NULL;
}
}
int erofs_xattr_prefixes_init(struct erofs_sb_info *sbi)
{
erofs_off_t pos = (erofs_off_t)sbi->xattr_prefix_start << 2;
struct erofs_xattr_prefix_item *pfs;
erofs_nid_t nid = 0;
int ret = 0, i, len;
void *buf;
if (!sbi->xattr_prefix_count)
return 0;
if (sbi->packed_nid)
nid = sbi->packed_nid;
pfs = calloc(sbi->xattr_prefix_count, sizeof(*pfs));
if (!pfs)
return -ENOMEM;
for (i = 0; i < sbi->xattr_prefix_count; i++) {
buf = erofs_read_metadata(sbi, nid, &pos, &len);
if (IS_ERR(buf)) {
ret = PTR_ERR(buf);
goto out;
}
if (len < sizeof(*pfs->prefix) ||
len > EROFS_NAME_LEN + sizeof(*pfs->prefix)) {
free(buf);
ret = -EFSCORRUPTED;
goto out;
}
pfs[i].prefix = buf;
pfs[i].infix_len = len - sizeof(struct erofs_xattr_long_prefix);
}
out:
sbi->xattr_prefixes = pfs;
if (ret)
erofs_xattr_prefixes_cleanup(sbi);
return ret;
}