From: Gou Hao gouhao@uniontech.com
uniontech inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/I40JRR CVE: NA
--------------
The block header is followed by multiple entry descriptors. These entry descriptors are variable in size, and aligned to EUFS_XATTR_PAD byte boundaries. The entry descriptors are sorted by attribute name, so that two extended attribute blocks can be compared efficiently, attribute name and value are stored in e_data, No additional gaps are left between them.
Signed-off-by: Gou Hao gouhao@uniontech.com --- fs/eulerfs/Makefile | 1 + fs/eulerfs/xattr.c | 416 ++++++++++++++++++++++++++++++++++++++++++++ fs/eulerfs/xattr.h | 98 +++++++++++ 3 files changed, 515 insertions(+) create mode 100644 fs/eulerfs/xattr.c create mode 100644 fs/eulerfs/xattr.h
diff --git a/fs/eulerfs/Makefile b/fs/eulerfs/Makefile index 706e6ebff77e..60803f44e586 100644 --- a/fs/eulerfs/Makefile +++ b/fs/eulerfs/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_EULER_FS) += eulerfs.o eulerfs-y := dir.o file.o inode.o namei.o super.o symlink.o eulerfs-y += dax.o dht.o dep.o nvalloc.o wear.o eulerfs-y += kmem_cache.o +eulerfs-$(CONFIG_EULER_FS_XATTR) += xattr.o diff --git a/fs/eulerfs/xattr.c b/fs/eulerfs/xattr.c new file mode 100644 index 000000000000..3dfa582156e8 --- /dev/null +++ b/fs/eulerfs/xattr.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2001-2003 Andreas Gruenbacher agruen@suse.de + * + * Fix by Harrison Xing harrison@mountainviewdata.com. + * Extended attributes for symlinks and special files added per + * suggestion of Luka Renko luka.renko@hermes.si. + * xattr consolidation Copyright (c) 2004 James Morris jmorris@redhat.com, + * Red Hat Inc. + * + * This file was modified by UnionTech Software Technology Co., Ltd. in + * 2022/07/05 by Gou Hao gouhao@uniontech.com, add the new xattr block + * layout. + */ + +/* + * Extended attributes are stored on disk blocks allocated outside of + * any inode. The i_xattr field is then made to point to this allocated + * block. If all extended attributes of an inode are identical, these + * inodes may share the same extended attribute block. Such situations + * are automatically detected by keeping a cache of recent attribute block + * numbers and hashes over the block's contents in memory. + * + * + * Extended attribute block layout: + * + * +------------------+ + * | header | + * | entry-value1entry| | + * | -value2... | | growing downwards + * | four null bytes | + * | | + * | | + * | | + * +------------------+ + * + * The block header is followed by multiple entry descriptors. These entry + * descriptors are variable in size, and aligned to EUFS_XATTR_PAD + * byte boundaries. The entry descriptors are sorted by attribute name, + * so that two extended attribute blocks can be compared efficiently, + * attribute name and value are stored in e_data, No additional gaps + * are left between them. + * + * Locking strategy + * ---------------- + * EUFS_I(inode)->i_xattr is protected by EUFS_I(inode)->xattr_rwsem. + * EA blocks are only changed if they are exclusive to an inode, so + * holding xattr_rwsem also means that nothing but the EA block's reference + * count will change. Multiple writers to an EA block are synchronized + * by the bh lock. No more than a single bh lock is held at any time + * to avoid deadlocks. + */ + +#include <linux/string.h> + +#include "euler.h" +#include "dep.h" +#include "xattr.h" + +#define eufs_xattr_dbg(format, args...) eufs_dbg("%s: " format, __func__, ##args) +#define eufs_xattr_err(sb, format, args...) eufs_err(sb, "%s: " format, __func__, ##args) + +#define HDR(ptr) ((struct eufs_xattr_header *)(ptr)) +#define ENTRY(ptr) ((struct eufs_xattr_entry *)(ptr)) +#define FIRST_ENTRY(ptr) ENTRY(HDR(ptr)+1) +#define IS_LAST_ENTRY(entry) (*(__u32 *)(entry) == 0) + +static inline bool +eufs_xattr_header_valid(struct eufs_xattr_header *header) +{ + return header->h_magic == cpu_to_le32(EUFS_XATTR_MAGIC) + && header->h_blocks == cpu_to_le32(1); +} + +static int +eufs_xattr_cmp_entry(int name_index, size_t name_len, const char *name, + struct eufs_xattr_entry *entry) +{ + int cmp; + + cmp = name_index - entry->e_name_index; + if (!cmp) + cmp = name_len - entry->e_name_len; + if (!cmp) + cmp = memcmp(name, entry->e_data, name_len); + + return cmp; +} + +const struct xattr_handler *eufs_xattr_handlers[] = { NULL }; +static const struct xattr_handler *eufs_xattr_handler_map[] = {}; + +static inline +const struct xattr_handler *eufs_xattr_handler(int name_index) +{ + if (name_index > 0 && name_index < ARRAY_SIZE(eufs_xattr_handler_map)) + return eufs_xattr_handler_map[name_index]; + return NULL; +} + +int eufs_xattr_get(struct inode *inode, int name_index, + const char *name, void *buffer, size_t buffer_size) +{ + struct eufs_xattr_entry *entry; + size_t name_len, size; + int error, not_found; + + struct eufs_inode_info *vi = EUFS_I(inode); + void *page = NULL; + struct super_block *sb = inode->i_sb; + + eufs_xattr_dbg("name=%s, buf_size=%ld, i_xattr=%ld\n", + name, (long)buffer_size, vi->i_xattr); + if (!name) + return -EINVAL; + + name_len = strlen(name); + if (name_len > EUFS_XATTR_NAME_MAX) + return -ERANGE; + + down_read(&vi->xattr_rwsem); + + error = -ENODATA; + if (!vi->i_xattr) + goto out; + + page = (void *)o2p(sb, vi->i_xattr); + error = -EIO; + if (!page || page == NULL_ADDR_PTR) + goto bad_block; + + if (!eufs_xattr_header_valid(HDR(page))) + goto bad_block; + + entry = FIRST_ENTRY(page); + while (!IS_LAST_ENTRY(entry)) { + not_found = eufs_xattr_cmp_entry(name_index, + name_len, name, entry); + if (!not_found) { + size = le32_to_cpu(entry->e_value_len); + + if (buffer) { + error = -ERANGE; + if (size > buffer_size) + goto out; + memcpy(buffer, entry->e_data + name_len, size); + } + error = size; + goto out; + } + + if (not_found < 0) + break; + + entry = EUFS_XATTR_NEXT(entry); + } + error = -ENODATA; + goto out; + +bad_block: + eufs_xattr_err(sb, "bad block: inode=%ld, block=%llu", + inode->i_ino, vi->i_xattr); +out: + up_read(&vi->xattr_rwsem); + return error; +} + +int eufs_xattr_set(struct inode *inode, int name_index, + const char *name, const void *value, + size_t value_len, int flags) +{ + struct super_block *sb = inode->i_sb; + struct eufs_inode_info *vi = EUFS_I(inode); + struct eufs_xattr_header *header = NULL; + struct eufs_xattr_entry *here = NULL, *last = NULL; + size_t block_size = sb->s_blocksize; + size_t name_len, free; + int not_found = 1, error = 0; + void *page = NULL; + size_t size, rest, new_entry_len, old_entry_len; + + eufs_xattr_dbg("name=%s, value_len=%ld, i_xattr=%llu, flags=%d\n", + name, (long)value_len, vi->i_xattr, flags); + if (!name) + return -EINVAL; + + name_len = strlen(name); + if (!value) + value_len = 0; + + if (name_len > EUFS_XATTR_NAME_MAX || value_len > block_size) + return -ERANGE; + + down_write(&vi->xattr_rwsem); + + free = block_size - sizeof(*header) - sizeof(__u32); + + if (vi->i_xattr) { + page = (void *)o2p(sb, vi->i_xattr); + error = -EIO; + if (!page || page == NULL_ADDR_PTR) + goto bad_block; + + header = HDR(page); + if (!eufs_xattr_header_valid(header)) + goto bad_block; + + last = FIRST_ENTRY(page); + while (!IS_LAST_ENTRY(last)) { + if (not_found > 0) { + not_found = eufs_xattr_cmp_entry(name_index, + name_len, name, last); + if (not_found <= 0) + here = last; + } + last = EUFS_XATTR_NEXT(last); + } + if (not_found > 0) + here = last; + free = block_size - ((char *)last - (char *)header) - sizeof(__u32); + } + + eufs_xattr_dbg("free=%lu\n", free); + + if (not_found) { + error = -ENODATA; + if (flags & XATTR_REPLACE) + goto out; + error = 0; + if (!value) + goto out; + } else { + error = -EEXIST; + if (flags & XATTR_CREATE) + goto out; + + free += EUFS_XATTR_ENTRY_SIZE(here); + } + + error = -ENOSPC; + if (free < EUFS_XATTR_LEN(name_len + value_len)) + goto out; + + if (!page) { + page = eufs_zalloc_inode_xattr(sb); + error = -ENOMEM; + if (!page) + goto out; + eufs_xattr_dbg("newpage=%llu\n", p2o(sb, page)); + + eufs_alloc_persist(inode->i_sb, page, false); + + header = HDR(page); + header->h_magic = cpu_to_le32(EUFS_XATTR_MAGIC); + header->h_blocks = header->h_refcount = cpu_to_le32(1); + last = here = ENTRY(header + 1); + } + + if (not_found) { + size = EUFS_XATTR_LEN(name_len + value_len); + rest = (char *)last - (char *)here; + + if (rest) { + memmove((char *)here + size, here, rest); + memset(here, 0, size); + } + + here->e_name_index = name_index; + here->e_name_len = name_len; + here->e_value_len = cpu_to_le32(value_len); + memcpy(here->e_data, name, name_len); + memcpy(here->e_data + name_len, value, value_len); + goto sync; + } else { + if (!value) { + size = EUFS_XATTR_ENTRY_SIZE(here); + if ((char *)last == (char *) here + size) { + memset(here, 0, size); + } else { + memmove(here, (char *)here + size, + (char *)last - (char *)here - size); + memset((char *)last - size, 0, size); + } + + } else { + new_entry_len = EUFS_XATTR_LEN(name_len + value_len); + old_entry_len = EUFS_XATTR_ENTRY_SIZE(here); + here->e_value_len = cpu_to_le32(value_len); + + if (new_entry_len != old_entry_len) { + memmove((char *)here + new_entry_len, + (char *)here + old_entry_len, + (char *)last - (char *)here - old_entry_len); + } + if (new_entry_len < old_entry_len) + memset((char *)last - (old_entry_len - new_entry_len), 0, + old_entry_len - new_entry_len); + memset(here + new_entry_len - EUFS_XATTR_PAD, 0, EUFS_XATTR_PAD); + memcpy(here->e_data + name_len, value, value_len); + } + } +sync: + + if (IS_LAST_ENTRY((ENTRY(header + 1)))) { + vi->i_xattr = 0; + memset(page, 0, block_size); + nv_free(sb, page); + } else { + eufs_flush_page(page); + vi->i_xattr = p2o(sb, page); + } + inode->i_ctime = current_time(inode); + eufs_sync_pinode(inode, EUFS_PI(inode), false); + persist_pinode(EUFS_PI(inode)); + + error = 0; + goto out; + +bad_block: + eufs_xattr_err(sb, "bad block: inode=%ld, block=%llu", + inode->i_ino, vi->i_xattr); +out: + up_write(&vi->xattr_rwsem); + return error; +} + +ssize_t eufs_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size) +{ + struct inode *inode = d_inode(dentry); + struct eufs_xattr_entry *entry; + size_t rest = buffer_size; + int error = 0; + + struct eufs_inode_info *vi = EUFS_I(inode); + void *page = NULL; + struct super_block *sb = inode->i_sb; + + eufs_xattr_dbg("buf_size=%ld, i_xattr=%ld\n", + (long)buffer_size, vi->i_xattr); + + down_read(&vi->xattr_rwsem); + if (!vi->i_xattr) + goto out; + + page = (void *)o2p(sb, vi->i_xattr); + error = -EIO; + if (!page || page == NULL_ADDR_PTR) + goto out; + + if (!eufs_xattr_header_valid(HDR(page))) + goto bad_block; + + for (entry = FIRST_ENTRY(page); !IS_LAST_ENTRY(entry); + entry = EUFS_XATTR_NEXT(entry)) { + const struct xattr_handler *handler = + eufs_xattr_handler(entry->e_name_index); + + if (handler && (!handler->list || handler->list(dentry))) { + const char *prefix = handler->prefix ?: handler->name; + size_t prefix_len = strlen(prefix); + size_t name_len = entry->e_name_len; + size_t size = prefix_len + name_len + 1; + + if (buffer) { + if (size > rest) { + error = -ERANGE; + goto out; + } + + memcpy(buffer, prefix, prefix_len); + buffer += prefix_len; + memcpy(buffer, entry->e_data, name_len); + buffer += name_len; + *buffer++ = 0; + } + rest -= size; + } + } + + error = buffer_size - rest; + + goto out; +bad_block: + error = -EIO; + eufs_xattr_err(sb, "bad block: inode=%ld, block=%llu", + inode->i_ino, vi->i_xattr); +out: + up_read(&vi->xattr_rwsem); + return error; +} + +void eufs_xattr_delete_inode(struct inode *inode) +{ + struct eufs_inode_info *vi = EUFS_I(inode); + struct super_block *sb = inode->i_sb; + void *page = NULL; + + down_write(&vi->xattr_rwsem); + + if (!vi->i_xattr) + goto out; + + page = (void *)o2p(sb, vi->i_xattr); + if (page == NULL_ADDR_PTR) + goto out; + + if (!eufs_xattr_header_valid(HDR(page))) { + eufs_xattr_err(sb, "inode=%ld, bad_block=%lld", + inode->i_ino, vi->i_xattr); + goto out; + } + vi->i_xattr = 0; + nv_free(sb, page); +out: + up_write(&vi->xattr_rwsem); +} diff --git a/fs/eulerfs/xattr.h b/fs/eulerfs/xattr.h new file mode 100644 index 000000000000..935fce3acb68 --- /dev/null +++ b/fs/eulerfs/xattr.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * On-disk format of extended attributes for the euler filesystem. + * + * (C) 2001 Andreas Gruenbacher, a.gruenbacher@computer.org + * + * + * This file was modified by UnionTech Software Technology Co., Ltd. in + * 2022/07/05 by Gou Hao gouhao@uniontech.com, add the new xattr block + * layout. + */ +#ifndef EUFS_XATTR_H +#define EUFS_XATTR_H + +#include <linux/xattr.h> + +/* Magic value in attribute blocks */ +#define EUFS_XATTR_MAGIC 0xEA002022 + +/* Name indexes */ +#define EUFS_XATTR_INDEX_USER 1 + +#define EUFS_XATTR_NAME_MAX 255 + + +struct eufs_xattr_header { + __le32 h_magic; /* magic number for identification */ + __le32 h_refcount; /* reference count */ + __le32 h_blocks; /* number of disk blocks used */ + __le32 h_hash; /* hash value of all attributes */ + __u32 h_reserved[4]; /* zero right now */ +}; + +struct eufs_xattr_entry { + __u8 e_name_index; /* attribute name index */ + __u8 e_name_len; + __le16 padding; + __le32 e_value_len; /* name and value len. name:0-7bit. value:8-31bit*/ + __le32 e_hash; /* hash value of name and value */ + char e_data[]; /* attribute name and value */ +}; + +#define EUFS_XATTR_PAD_BITS 2 +#define EUFS_XATTR_PAD (1<<EUFS_XATTR_PAD_BITS) +#define EUFS_XATTR_ROUND (EUFS_XATTR_PAD-1) + +#define EUFS_XATTR_LEN(len) \ + (((len) + EUFS_XATTR_ROUND + \ + sizeof(struct eufs_xattr_entry)) & ~EUFS_XATTR_ROUND) + +#define EUFS_XATTR_ENTRY_SIZE(entry) \ + EUFS_XATTR_LEN((entry)->e_name_len + le32_to_cpu((entry)->e_value_len)) + +#define EUFS_XATTR_NEXT(entry) \ + ( (struct eufs_xattr_entry *)( \ + (char *)(entry) + EUFS_XATTR_ENTRY_SIZE((entry)))) + + + +#ifdef CONFIG_EULER_FS_XATTR + +extern const struct xattr_handler *eufs_xattr_handlers[]; + +extern int eufs_xattr_get(struct inode *inode, int name_index, + const char *name, void *value, size_t value_len); +extern int eufs_xattr_set(struct inode *inode, int name_index, + const char *name, const void *value, size_t value_len, int flags); +extern ssize_t eufs_listxattr(struct dentry *dentry, char *buf, size_t buf_len); + +extern void eufs_xattr_delete_inode(struct inode *inode); + +#else /* CONFIG_EULER_FS_XATTR */ + +#define eufs_xattr_handlers NULL +#define eufs_listxattr NULL + +static inline int +eufs_xattr_get(struct inode *inode, int name_index, + const char *name, void *buffer, size_t size) +{ + return -EOPNOTSUPP; +} + +static inline int +eufs_xattr_set(struct inode *inode, int name_index, const char *name, + const void *value, size_t size, int flags) +{ + return -EOPNOTSUPP; +} + +static inline void +eufs_xattr_delete_inode(struct inode *inode) +{ +} + +#endif /* CONFIG_EULER_FS_XATTR */ + +#endif /* EUFS_XATTR_H */