You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
665 lines
14 KiB
665 lines
14 KiB
// 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>
|
|
*/
|
|
#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"
|
|
|
|
#define EA_HASHTABLE_BITS 16
|
|
|
|
struct xattr_item {
|
|
const char *kvbuf;
|
|
unsigned int hash[2], len[2], count;
|
|
int shared_xattr_id;
|
|
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 LIST_HEAD(shared_xattrs_list);
|
|
static unsigned int shared_xattrs_count, shared_xattrs_size;
|
|
|
|
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->shared_xattr_id = -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 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;
|
|
|
|
#ifdef WITH_ANDROID
|
|
if (cfg.mount_point)
|
|
ret = asprintf(&fspath, "/%s/%s", cfg.mount_point,
|
|
erofs_fspath(srcpath));
|
|
else
|
|
#endif
|
|
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("selinux") - 1;
|
|
len[1] = strlen(secontext);
|
|
kvbuf = malloc(len[0] + len[1] + 1);
|
|
if (!kvbuf) {
|
|
freecon(secontext);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
sprintf(kvbuf, "selinux%s", secontext);
|
|
freecon(secontext);
|
|
return get_xattritem(EROFS_XATTR_INDEX_SECURITY, 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)
|
|
{
|
|
struct inode_xattr_node *node = malloc(sizeof(*node));
|
|
|
|
if (!node)
|
|
return -ENOMEM;
|
|
|
|
init_list_head(&node->list);
|
|
node->item = item;
|
|
list_add(&node->list, &shared_xattrs_list);
|
|
|
|
shared_xattrs_size += sizeof(struct erofs_xattr_entry);
|
|
shared_xattrs_size = EROFS_XATTR_ALIGN(shared_xattrs_size +
|
|
item->len[0] + item->len[1]);
|
|
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
|
|
return false;
|
|
}
|
|
|
|
static int read_xattrs_from_file(const char *path, mode_t mode,
|
|
struct list_head *ixattrs)
|
|
{
|
|
ssize_t kllen = llistxattr(path, NULL, 0);
|
|
int ret;
|
|
char *keylst, *key, *klend;
|
|
unsigned int keylen;
|
|
struct xattr_item *item;
|
|
|
|
if (kllen < 0 && errno != ENODATA) {
|
|
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.*/
|
|
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.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
#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("capability") - 1;
|
|
len[1] = sizeof(caps);
|
|
|
|
kvbuf = malloc(len[0] + len[1]);
|
|
if (!kvbuf)
|
|
return -ENOMEM;
|
|
|
|
memcpy(kvbuf, "capability", len[0]);
|
|
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 + len[0], &caps, len[1]);
|
|
|
|
item = get_xattritem(EROFS_XATTR_INDEX_SECURITY, kvbuf, len);
|
|
if (IS_ERR(item))
|
|
return PTR_ERR(item);
|
|
if (!item)
|
|
return 0;
|
|
|
|
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_prepare_xattr_ibody(struct erofs_inode *inode)
|
|
{
|
|
int ret;
|
|
struct inode_xattr_node *node;
|
|
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;
|
|
|
|
ret = erofs_droid_xattr_set_caps(inode);
|
|
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;
|
|
|
|
if (item->shared_xattr_id >= 0) {
|
|
ret += sizeof(__le32);
|
|
continue;
|
|
}
|
|
ret += sizeof(struct erofs_xattr_entry);
|
|
ret = EROFS_XATTR_ALIGN(ret + item->len[0] + item->len[1]);
|
|
}
|
|
inode->xattr_isize = ret;
|
|
return ret;
|
|
}
|
|
|
|
static int erofs_count_all_xattrs_from_path(const char *path)
|
|
{
|
|
int ret;
|
|
DIR *_dir;
|
|
struct stat64 st;
|
|
|
|
_dir = opendir(path);
|
|
if (!_dir) {
|
|
erofs_err("%s, failed to opendir at %s: %s",
|
|
__func__, 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 = lstat64(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_size = shared_xattrs_count = 0;
|
|
}
|
|
|
|
static bool erofs_bh_flush_write_shared_xattrs(struct erofs_buffer_head *bh)
|
|
{
|
|
void *buf = bh->fsprivate;
|
|
int err = dev_write(buf, erofs_btell(bh, false), shared_xattrs_size);
|
|
|
|
if (err)
|
|
return false;
|
|
free(buf);
|
|
return erofs_bh_flush_generic_end(bh);
|
|
}
|
|
|
|
static struct erofs_bhops erofs_write_shared_xattrs_bhops = {
|
|
.flush = erofs_bh_flush_write_shared_xattrs,
|
|
};
|
|
|
|
int erofs_build_shared_xattrs_from_path(const char *path)
|
|
{
|
|
int ret;
|
|
struct erofs_buffer_head *bh;
|
|
struct inode_xattr_node *node, *n;
|
|
char *buf;
|
|
unsigned int p;
|
|
erofs_off_t off;
|
|
|
|
/* 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_size || shared_xattrs_count) {
|
|
DBG_BUGON(1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = erofs_count_all_xattrs_from_path(path);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!shared_xattrs_size)
|
|
goto out;
|
|
|
|
buf = calloc(1, shared_xattrs_size);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
bh = erofs_balloc(XATTR, shared_xattrs_size, 0, 0);
|
|
if (IS_ERR(bh)) {
|
|
free(buf);
|
|
return PTR_ERR(bh);
|
|
}
|
|
bh->op = &erofs_skip_write_bhops;
|
|
|
|
erofs_mapbh(bh->block, true);
|
|
off = erofs_btell(bh, false);
|
|
|
|
sbi.xattr_blkaddr = off / EROFS_BLKSIZ;
|
|
off %= EROFS_BLKSIZ;
|
|
p = 0;
|
|
|
|
list_for_each_entry_safe(node, n, &shared_xattrs_list, 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])
|
|
};
|
|
|
|
list_del(&node->list);
|
|
|
|
item->shared_xattr_id = (off + p) /
|
|
sizeof(struct erofs_xattr_entry);
|
|
|
|
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]);
|
|
free(node);
|
|
}
|
|
bh->fsprivate = buf;
|
|
bh->op = &erofs_write_shared_xattrs_bhops;
|
|
out:
|
|
erofs_cleanxattrs(true);
|
|
return 0;
|
|
}
|
|
|
|
char *erofs_export_xattr_ibody(struct list_head *ixattrs, unsigned int size)
|
|
{
|
|
struct inode_xattr_node *node, *n;
|
|
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;
|
|
|
|
p = sizeof(struct erofs_xattr_ibody_header);
|
|
list_for_each_entry_safe(node, n, ixattrs, list) {
|
|
struct xattr_item *const 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) {
|
|
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;
|
|
}
|
|
|