| // 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("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 = 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); |
| 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; |
| } |
| |