| /* |
| * ext_attr.c --- extended attribute blocks |
| * |
| * Copyright (C) 2001 Andreas Gruenbacher, <a.gruenbacher@computer.org> |
| * |
| * Copyright (C) 2002 Theodore Ts'o. |
| * |
| * %Begin-Header% |
| * This file may be redistributed under the terms of the GNU Library |
| * General Public License, version 2. |
| * %End-Header% |
| */ |
| |
| #include "config.h" |
| #include <stdio.h> |
| #if HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| #include <string.h> |
| #include <time.h> |
| |
| #include "ext2_fs.h" |
| #include "ext2_ext_attr.h" |
| #include "ext4_acl.h" |
| |
| #include "ext2fs.h" |
| |
| static errcode_t read_ea_inode_hash(ext2_filsys fs, ext2_ino_t ino, __u32 *hash) |
| { |
| struct ext2_inode inode; |
| errcode_t retval; |
| |
| retval = ext2fs_read_inode(fs, ino, &inode); |
| if (retval) |
| return retval; |
| *hash = ext2fs_get_ea_inode_hash(&inode); |
| return 0; |
| } |
| |
| #define NAME_HASH_SHIFT 5 |
| #define VALUE_HASH_SHIFT 16 |
| |
| /* |
| * ext2_xattr_hash_entry() |
| * |
| * Compute the hash of an extended attribute. |
| */ |
| __u32 ext2fs_ext_attr_hash_entry(struct ext2_ext_attr_entry *entry, void *data) |
| { |
| __u32 hash = 0; |
| char *name = ((char *) entry) + sizeof(struct ext2_ext_attr_entry); |
| int n; |
| |
| for (n = 0; n < entry->e_name_len; n++) { |
| hash = (hash << NAME_HASH_SHIFT) ^ |
| (hash >> (8*sizeof(hash) - NAME_HASH_SHIFT)) ^ |
| *name++; |
| } |
| |
| /* The hash needs to be calculated on the data in little-endian. */ |
| if (entry->e_value_inum == 0 && entry->e_value_size != 0) { |
| __u32 *value = (__u32 *)data; |
| for (n = (entry->e_value_size + EXT2_EXT_ATTR_ROUND) >> |
| EXT2_EXT_ATTR_PAD_BITS; n; n--) { |
| hash = (hash << VALUE_HASH_SHIFT) ^ |
| (hash >> (8*sizeof(hash) - VALUE_HASH_SHIFT)) ^ |
| ext2fs_le32_to_cpu(*value++); |
| } |
| } |
| |
| return hash; |
| } |
| |
| /* |
| * ext2fs_ext_attr_hash_entry2() |
| * |
| * Compute the hash of an extended attribute. |
| * This version of the function supports hashing entries that reference |
| * external inodes (ea_inode feature). |
| */ |
| errcode_t ext2fs_ext_attr_hash_entry2(ext2_filsys fs, |
| struct ext2_ext_attr_entry *entry, |
| void *data, __u32 *hash) |
| { |
| *hash = ext2fs_ext_attr_hash_entry(entry, data); |
| |
| if (entry->e_value_inum) { |
| __u32 ea_inode_hash; |
| errcode_t retval; |
| |
| retval = read_ea_inode_hash(fs, entry->e_value_inum, |
| &ea_inode_hash); |
| if (retval) |
| return retval; |
| |
| *hash = (*hash << VALUE_HASH_SHIFT) ^ |
| (*hash >> (8*sizeof(*hash) - VALUE_HASH_SHIFT)) ^ |
| ea_inode_hash; |
| } |
| return 0; |
| } |
| |
| #undef NAME_HASH_SHIFT |
| #undef VALUE_HASH_SHIFT |
| |
| #define BLOCK_HASH_SHIFT 16 |
| |
| /* Mirrors ext4_xattr_rehash() implementation in kernel. */ |
| void ext2fs_ext_attr_block_rehash(struct ext2_ext_attr_header *header, |
| struct ext2_ext_attr_entry *end) |
| { |
| struct ext2_ext_attr_entry *here; |
| __u32 hash = 0; |
| |
| here = (struct ext2_ext_attr_entry *)(header+1); |
| while (here < end && !EXT2_EXT_IS_LAST_ENTRY(here)) { |
| if (!here->e_hash) { |
| /* Block is not shared if an entry's hash value == 0 */ |
| hash = 0; |
| break; |
| } |
| hash = (hash << BLOCK_HASH_SHIFT) ^ |
| (hash >> (8*sizeof(hash) - BLOCK_HASH_SHIFT)) ^ |
| here->e_hash; |
| here = EXT2_EXT_ATTR_NEXT(here); |
| } |
| header->h_hash = hash; |
| } |
| |
| #undef BLOCK_HASH_SHIFT |
| |
| __u32 ext2fs_get_ea_inode_hash(struct ext2_inode *inode) |
| { |
| return inode->i_atime; |
| } |
| |
| void ext2fs_set_ea_inode_hash(struct ext2_inode *inode, __u32 hash) |
| { |
| inode->i_atime = hash; |
| } |
| |
| __u64 ext2fs_get_ea_inode_ref(struct ext2_inode *inode) |
| { |
| return ((__u64)inode->i_ctime << 32) | inode->osd1.linux1.l_i_version; |
| } |
| |
| void ext2fs_set_ea_inode_ref(struct ext2_inode *inode, __u64 ref_count) |
| { |
| inode->i_ctime = (__u32)(ref_count >> 32); |
| inode->osd1.linux1.l_i_version = (__u32)ref_count; |
| } |
| |
| static errcode_t check_ext_attr_header(struct ext2_ext_attr_header *header) |
| { |
| if ((header->h_magic != EXT2_EXT_ATTR_MAGIC_v1 && |
| header->h_magic != EXT2_EXT_ATTR_MAGIC) || |
| header->h_blocks != 1) |
| return EXT2_ET_BAD_EA_HEADER; |
| |
| return 0; |
| } |
| |
| errcode_t ext2fs_read_ext_attr3(ext2_filsys fs, blk64_t block, void *buf, |
| ext2_ino_t inum) |
| { |
| int csum_failed = 0; |
| errcode_t retval; |
| |
| retval = io_channel_read_blk64(fs->io, block, 1, buf); |
| if (retval) |
| return retval; |
| |
| if (!(fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) && |
| !ext2fs_ext_attr_block_csum_verify(fs, inum, block, buf)) |
| csum_failed = 1; |
| |
| #ifdef WORDS_BIGENDIAN |
| ext2fs_swap_ext_attr(buf, buf, fs->blocksize, 1); |
| #endif |
| |
| retval = check_ext_attr_header(buf); |
| if (retval == 0 && csum_failed) |
| retval = EXT2_ET_EXT_ATTR_CSUM_INVALID; |
| |
| return retval; |
| } |
| |
| errcode_t ext2fs_read_ext_attr2(ext2_filsys fs, blk64_t block, void *buf) |
| { |
| return ext2fs_read_ext_attr3(fs, block, buf, 0); |
| } |
| |
| errcode_t ext2fs_read_ext_attr(ext2_filsys fs, blk_t block, void *buf) |
| { |
| return ext2fs_read_ext_attr2(fs, block, buf); |
| } |
| |
| errcode_t ext2fs_write_ext_attr3(ext2_filsys fs, blk64_t block, void *inbuf, |
| ext2_ino_t inum) |
| { |
| errcode_t retval; |
| char *write_buf; |
| |
| #ifdef WORDS_BIGENDIAN |
| retval = ext2fs_get_mem(fs->blocksize, &write_buf); |
| if (retval) |
| return retval; |
| ext2fs_swap_ext_attr(write_buf, inbuf, fs->blocksize, 1); |
| #else |
| write_buf = (char *) inbuf; |
| #endif |
| |
| retval = ext2fs_ext_attr_block_csum_set(fs, inum, block, |
| (struct ext2_ext_attr_header *)write_buf); |
| if (retval) |
| return retval; |
| |
| retval = io_channel_write_blk64(fs->io, block, 1, write_buf); |
| #ifdef WORDS_BIGENDIAN |
| ext2fs_free_mem(&write_buf); |
| #endif |
| if (!retval) |
| ext2fs_mark_changed(fs); |
| return retval; |
| } |
| |
| errcode_t ext2fs_write_ext_attr2(ext2_filsys fs, blk64_t block, void *inbuf) |
| { |
| return ext2fs_write_ext_attr3(fs, block, inbuf, 0); |
| } |
| |
| errcode_t ext2fs_write_ext_attr(ext2_filsys fs, blk_t block, void *inbuf) |
| { |
| return ext2fs_write_ext_attr2(fs, block, inbuf); |
| } |
| |
| /* |
| * This function adjusts the reference count of the EA block. |
| */ |
| errcode_t ext2fs_adjust_ea_refcount3(ext2_filsys fs, blk64_t blk, |
| char *block_buf, int adjust, |
| __u32 *newcount, ext2_ino_t inum) |
| { |
| errcode_t retval; |
| struct ext2_ext_attr_header *header; |
| char *buf = 0; |
| |
| if ((blk >= ext2fs_blocks_count(fs->super)) || |
| (blk < fs->super->s_first_data_block)) |
| return EXT2_ET_BAD_EA_BLOCK_NUM; |
| |
| if (!block_buf) { |
| retval = ext2fs_get_mem(fs->blocksize, &buf); |
| if (retval) |
| return retval; |
| block_buf = buf; |
| } |
| |
| retval = ext2fs_read_ext_attr3(fs, blk, block_buf, inum); |
| if (retval) |
| goto errout; |
| |
| header = (struct ext2_ext_attr_header *) block_buf; |
| header->h_refcount += adjust; |
| if (newcount) |
| *newcount = header->h_refcount; |
| |
| retval = ext2fs_write_ext_attr3(fs, blk, block_buf, inum); |
| if (retval) |
| goto errout; |
| |
| errout: |
| if (buf) |
| ext2fs_free_mem(&buf); |
| return retval; |
| } |
| |
| errcode_t ext2fs_adjust_ea_refcount2(ext2_filsys fs, blk64_t blk, |
| char *block_buf, int adjust, |
| __u32 *newcount) |
| { |
| return ext2fs_adjust_ea_refcount3(fs, blk, block_buf, adjust, |
| newcount, 0); |
| } |
| |
| errcode_t ext2fs_adjust_ea_refcount(ext2_filsys fs, blk_t blk, |
| char *block_buf, int adjust, |
| __u32 *newcount) |
| { |
| return ext2fs_adjust_ea_refcount2(fs, blk, block_buf, adjust, |
| newcount); |
| } |
| |
| /* Manipulate the contents of extended attribute regions */ |
| struct ext2_xattr { |
| char *name; |
| void *value; |
| unsigned int value_len; |
| ext2_ino_t ea_ino; |
| }; |
| |
| struct ext2_xattr_handle { |
| errcode_t magic; |
| ext2_filsys fs; |
| struct ext2_xattr *attrs; |
| int capacity; |
| int count; |
| int ibody_count; |
| ext2_ino_t ino; |
| unsigned int flags; |
| }; |
| |
| static errcode_t ext2fs_xattrs_expand(struct ext2_xattr_handle *h, |
| unsigned int expandby) |
| { |
| struct ext2_xattr *new_attrs; |
| errcode_t err; |
| |
| err = ext2fs_get_arrayzero(h->capacity + expandby, |
| sizeof(struct ext2_xattr), &new_attrs); |
| if (err) |
| return err; |
| |
| memcpy(new_attrs, h->attrs, h->capacity * sizeof(struct ext2_xattr)); |
| ext2fs_free_mem(&h->attrs); |
| h->capacity += expandby; |
| h->attrs = new_attrs; |
| |
| return 0; |
| } |
| |
| struct ea_name_index { |
| int index; |
| const char *name; |
| }; |
| |
| /* Keep these names sorted in order of decreasing specificity. */ |
| static struct ea_name_index ea_names[] = { |
| {3, "system.posix_acl_default"}, |
| {2, "system.posix_acl_access"}, |
| {8, "system.richacl"}, |
| {6, "security."}, |
| {4, "trusted."}, |
| {7, "system."}, |
| {1, "user."}, |
| {0, NULL}, |
| }; |
| |
| static const char *find_ea_prefix(int index) |
| { |
| struct ea_name_index *e; |
| |
| for (e = ea_names; e->name; e++) |
| if (e->index == index) |
| return e->name; |
| |
| return NULL; |
| } |
| |
| static int find_ea_index(const char *fullname, const char **name, int *index) |
| { |
| struct ea_name_index *e; |
| |
| for (e = ea_names; e->name; e++) { |
| if (strncmp(fullname, e->name, strlen(e->name)) == 0) { |
| *name = fullname + strlen(e->name); |
| *index = e->index; |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| errcode_t ext2fs_free_ext_attr(ext2_filsys fs, ext2_ino_t ino, |
| struct ext2_inode_large *inode) |
| { |
| struct ext2_ext_attr_header *header; |
| void *block_buf = NULL; |
| blk64_t blk; |
| errcode_t err; |
| struct ext2_inode_large i; |
| |
| /* Read inode? */ |
| if (inode == NULL) { |
| err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&i, |
| sizeof(struct ext2_inode_large)); |
| if (err) |
| return err; |
| inode = &i; |
| } |
| |
| /* Do we already have an EA block? */ |
| blk = ext2fs_file_acl_block(fs, (struct ext2_inode *)inode); |
| if (blk == 0) |
| return 0; |
| |
| /* Find block, zero it, write back */ |
| if ((blk < fs->super->s_first_data_block) || |
| (blk >= ext2fs_blocks_count(fs->super))) { |
| err = EXT2_ET_BAD_EA_BLOCK_NUM; |
| goto out; |
| } |
| |
| err = ext2fs_get_mem(fs->blocksize, &block_buf); |
| if (err) |
| goto out; |
| |
| err = ext2fs_read_ext_attr3(fs, blk, block_buf, ino); |
| if (err) |
| goto out2; |
| |
| /* We only know how to deal with v2 EA blocks */ |
| header = (struct ext2_ext_attr_header *) block_buf; |
| if (header->h_magic != EXT2_EXT_ATTR_MAGIC) { |
| err = EXT2_ET_BAD_EA_HEADER; |
| goto out2; |
| } |
| |
| header->h_refcount--; |
| err = ext2fs_write_ext_attr3(fs, blk, block_buf, ino); |
| if (err) |
| goto out2; |
| |
| /* Erase link to block */ |
| ext2fs_file_acl_block_set(fs, (struct ext2_inode *)inode, 0); |
| if (header->h_refcount == 0) |
| ext2fs_block_alloc_stats2(fs, blk, -1); |
| err = ext2fs_iblk_sub_blocks(fs, (struct ext2_inode *)inode, 1); |
| if (err) |
| goto out2; |
| |
| /* Write inode? */ |
| if (inode == &i) { |
| err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&i, |
| sizeof(struct ext2_inode_large)); |
| if (err) |
| goto out2; |
| } |
| |
| out2: |
| ext2fs_free_mem(&block_buf); |
| out: |
| return err; |
| } |
| |
| static errcode_t prep_ea_block_for_write(ext2_filsys fs, ext2_ino_t ino, |
| struct ext2_inode_large *inode) |
| { |
| struct ext2_ext_attr_header *header; |
| void *block_buf = NULL; |
| blk64_t blk, goal; |
| errcode_t err; |
| |
| /* Do we already have an EA block? */ |
| blk = ext2fs_file_acl_block(fs, (struct ext2_inode *)inode); |
| if (blk != 0) { |
| if ((blk < fs->super->s_first_data_block) || |
| (blk >= ext2fs_blocks_count(fs->super))) { |
| err = EXT2_ET_BAD_EA_BLOCK_NUM; |
| goto out; |
| } |
| |
| err = ext2fs_get_mem(fs->blocksize, &block_buf); |
| if (err) |
| goto out; |
| |
| err = ext2fs_read_ext_attr3(fs, blk, block_buf, ino); |
| if (err) |
| goto out2; |
| |
| /* We only know how to deal with v2 EA blocks */ |
| header = (struct ext2_ext_attr_header *) block_buf; |
| if (header->h_magic != EXT2_EXT_ATTR_MAGIC) { |
| err = EXT2_ET_BAD_EA_HEADER; |
| goto out2; |
| } |
| |
| /* Single-user block. We're done here. */ |
| if (header->h_refcount == 1) |
| goto out2; |
| |
| /* We need to CoW the block. */ |
| header->h_refcount--; |
| err = ext2fs_write_ext_attr3(fs, blk, block_buf, ino); |
| if (err) |
| goto out2; |
| } else { |
| /* No block, we must increment i_blocks */ |
| err = ext2fs_iblk_add_blocks(fs, (struct ext2_inode *)inode, |
| 1); |
| if (err) |
| goto out; |
| } |
| |
| /* Allocate a block */ |
| goal = ext2fs_find_inode_goal(fs, ino, (struct ext2_inode *)inode, 0); |
| err = ext2fs_alloc_block2(fs, goal, NULL, &blk); |
| if (err) |
| goto out2; |
| ext2fs_file_acl_block_set(fs, (struct ext2_inode *)inode, blk); |
| out2: |
| if (block_buf) |
| ext2fs_free_mem(&block_buf); |
| out: |
| return err; |
| } |
| |
| |
| static inline int |
| posix_acl_xattr_count(size_t size) |
| { |
| if (size < sizeof(posix_acl_xattr_header)) |
| return -1; |
| size -= sizeof(posix_acl_xattr_header); |
| if (size % sizeof(posix_acl_xattr_entry)) |
| return -1; |
| return size / sizeof(posix_acl_xattr_entry); |
| } |
| |
| /* |
| * The lgetxattr function returns data formatted in the POSIX extended |
| * attribute format. The on-disk format uses a more compact encoding. |
| * See the ext4_acl_to_disk in fs/ext4/acl.c. |
| */ |
| static errcode_t convert_posix_acl_to_disk_buffer(const void *value, size_t size, |
| void *out_buf, size_t *size_out) |
| { |
| const posix_acl_xattr_header *header = |
| (const posix_acl_xattr_header*) value; |
| const posix_acl_xattr_entry *end, *entry = |
| (const posix_acl_xattr_entry *)(header+1); |
| ext4_acl_header *ext_acl; |
| size_t s; |
| char *e; |
| |
| int count; |
| |
| if (!value) |
| return EINVAL; |
| if (size < sizeof(posix_acl_xattr_header)) |
| return ENOMEM; |
| if (header->a_version != ext2fs_cpu_to_le32(POSIX_ACL_XATTR_VERSION)) |
| return EINVAL; |
| |
| count = posix_acl_xattr_count(size); |
| ext_acl = out_buf; |
| ext_acl->a_version = ext2fs_cpu_to_le32(EXT4_ACL_VERSION); |
| |
| if (count <= 0) |
| return EINVAL; |
| |
| e = (char *) out_buf + sizeof(ext4_acl_header); |
| s = sizeof(ext4_acl_header); |
| for (end = entry + count; entry != end;entry++) { |
| ext4_acl_entry *disk_entry = (ext4_acl_entry*) e; |
| disk_entry->e_tag = ext2fs_cpu_to_le16(entry->e_tag); |
| disk_entry->e_perm = ext2fs_cpu_to_le16(entry->e_perm); |
| |
| switch(entry->e_tag) { |
| case ACL_USER_OBJ: |
| case ACL_GROUP_OBJ: |
| case ACL_MASK: |
| case ACL_OTHER: |
| e += sizeof(ext4_acl_entry_short); |
| s += sizeof(ext4_acl_entry_short); |
| break; |
| case ACL_USER: |
| case ACL_GROUP: |
| disk_entry->e_id = ext2fs_cpu_to_le32(entry->e_id); |
| e += sizeof(ext4_acl_entry); |
| s += sizeof(ext4_acl_entry); |
| break; |
| } |
| } |
| *size_out = s; |
| return 0; |
| } |
| |
| static errcode_t convert_disk_buffer_to_posix_acl(const void *value, size_t size, |
| void **out_buf, size_t *size_out) |
| { |
| posix_acl_xattr_header *header; |
| posix_acl_xattr_entry *entry; |
| const ext4_acl_header *ext_acl = (const ext4_acl_header *) value; |
| errcode_t err; |
| const char *cp; |
| char *out; |
| |
| if ((!value) || |
| (size < sizeof(ext4_acl_header)) || |
| (ext_acl->a_version != ext2fs_cpu_to_le32(EXT4_ACL_VERSION))) |
| return EINVAL; |
| |
| err = ext2fs_get_mem(size * 2, &out); |
| if (err) |
| return err; |
| |
| header = (posix_acl_xattr_header *) out; |
| header->a_version = ext2fs_cpu_to_le32(POSIX_ACL_XATTR_VERSION); |
| entry = (posix_acl_xattr_entry *) (out + sizeof(posix_acl_xattr_header)); |
| |
| cp = (const char *) value + sizeof(ext4_acl_header); |
| size -= sizeof(ext4_acl_header); |
| |
| while (size > 0) { |
| const ext4_acl_entry *disk_entry = (const ext4_acl_entry *) cp; |
| |
| entry->e_tag = ext2fs_le16_to_cpu(disk_entry->e_tag); |
| entry->e_perm = ext2fs_le16_to_cpu(disk_entry->e_perm); |
| |
| switch(entry->e_tag) { |
| case ACL_USER_OBJ: |
| case ACL_GROUP_OBJ: |
| case ACL_MASK: |
| case ACL_OTHER: |
| entry->e_id = 0; |
| cp += sizeof(ext4_acl_entry_short); |
| size -= sizeof(ext4_acl_entry_short); |
| break; |
| case ACL_USER: |
| case ACL_GROUP: |
| entry->e_id = ext2fs_le32_to_cpu(disk_entry->e_id); |
| cp += sizeof(ext4_acl_entry); |
| size -= sizeof(ext4_acl_entry); |
| break; |
| default: |
| ext2fs_free_mem(&out); |
| return EINVAL; |
| break; |
| } |
| entry++; |
| } |
| *out_buf = out; |
| *size_out = ((char *) entry - out); |
| return 0; |
| } |
| |
| static errcode_t |
| write_xattrs_to_buffer(ext2_filsys fs, struct ext2_xattr *attrs, int count, |
| void *entries_start, unsigned int storage_size, |
| unsigned int value_offset_correction, int write_hash) |
| { |
| struct ext2_xattr *x; |
| struct ext2_ext_attr_entry *e = entries_start; |
| char *end = (char *) entries_start + storage_size; |
| const char *shortname; |
| unsigned int value_size; |
| int idx, ret; |
| errcode_t err; |
| |
| memset(entries_start, 0, storage_size); |
| for (x = attrs; x < attrs + count; x++) { |
| /* Calculate index and shortname position */ |
| shortname = x->name; |
| ret = find_ea_index(x->name, &shortname, &idx); |
| |
| value_size = ((x->value_len + EXT2_EXT_ATTR_PAD - 1) / |
| EXT2_EXT_ATTR_PAD) * EXT2_EXT_ATTR_PAD; |
| |
| /* Fill out e appropriately */ |
| e->e_name_len = strlen(shortname); |
| e->e_name_index = (ret ? idx : 0); |
| |
| e->e_value_size = x->value_len; |
| e->e_value_inum = x->ea_ino; |
| |
| /* Store name */ |
| memcpy((char *)e + sizeof(*e), shortname, e->e_name_len); |
| if (x->ea_ino) { |
| e->e_value_offs = 0; |
| } else { |
| end -= value_size; |
| e->e_value_offs = end - (char *) entries_start + |
| value_offset_correction; |
| memcpy(end, x->value, e->e_value_size); |
| } |
| |
| if (write_hash || x->ea_ino) { |
| err = ext2fs_ext_attr_hash_entry2(fs, e, |
| x->ea_ino ? 0 : end, |
| &e->e_hash); |
| if (err) |
| return err; |
| } else |
| e->e_hash = 0; |
| |
| e = EXT2_EXT_ATTR_NEXT(e); |
| *(__u32 *)e = 0; |
| } |
| return 0; |
| } |
| |
| errcode_t ext2fs_xattrs_write(struct ext2_xattr_handle *handle) |
| { |
| ext2_filsys fs = handle->fs; |
| const unsigned int inode_size = EXT2_INODE_SIZE(fs->super); |
| struct ext2_inode_large *inode; |
| char *start, *block_buf = NULL; |
| struct ext2_ext_attr_header *header; |
| __u32 ea_inode_magic; |
| blk64_t blk; |
| unsigned int storage_size; |
| unsigned int i; |
| errcode_t err; |
| |
| EXT2_CHECK_MAGIC(handle, EXT2_ET_MAGIC_EA_HANDLE); |
| i = inode_size; |
| if (i < sizeof(*inode)) |
| i = sizeof(*inode); |
| err = ext2fs_get_memzero(i, &inode); |
| if (err) |
| return err; |
| |
| err = ext2fs_read_inode_full(fs, handle->ino, EXT2_INODE(inode), |
| inode_size); |
| if (err) |
| goto out; |
| |
| /* If extra_isize isn't set, we need to set it now */ |
| if (inode->i_extra_isize == 0 && |
| inode_size > EXT2_GOOD_OLD_INODE_SIZE) { |
| char *p = (char *)inode; |
| size_t extra = fs->super->s_want_extra_isize; |
| |
| if (extra == 0) |
| extra = sizeof(__u32); |
| memset(p + EXT2_GOOD_OLD_INODE_SIZE, 0, extra); |
| inode->i_extra_isize = extra; |
| } |
| if (inode->i_extra_isize & 3) { |
| err = EXT2_ET_INODE_CORRUPTED; |
| goto out; |
| } |
| |
| /* Does the inode have space for EA? */ |
| if (inode->i_extra_isize < sizeof(inode->i_extra_isize) || |
| inode_size <= EXT2_GOOD_OLD_INODE_SIZE + inode->i_extra_isize + |
| sizeof(__u32)) |
| goto write_ea_block; |
| |
| /* Write the inode EA */ |
| ea_inode_magic = EXT2_EXT_ATTR_MAGIC; |
| memcpy(((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + |
| inode->i_extra_isize, &ea_inode_magic, sizeof(__u32)); |
| storage_size = inode_size - EXT2_GOOD_OLD_INODE_SIZE - |
| inode->i_extra_isize - sizeof(__u32); |
| start = ((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + |
| inode->i_extra_isize + sizeof(__u32); |
| |
| err = write_xattrs_to_buffer(fs, handle->attrs, handle->ibody_count, |
| start, storage_size, 0, 0); |
| if (err) |
| goto out; |
| write_ea_block: |
| /* Are we done? */ |
| if (handle->ibody_count == handle->count && |
| !ext2fs_file_acl_block(fs, EXT2_INODE(inode))) |
| goto skip_ea_block; |
| |
| /* Write the EA block */ |
| err = ext2fs_get_memzero(fs->blocksize, &block_buf); |
| if (err) |
| goto out; |
| |
| storage_size = fs->blocksize - sizeof(struct ext2_ext_attr_header); |
| start = block_buf + sizeof(struct ext2_ext_attr_header); |
| |
| err = write_xattrs_to_buffer(fs, handle->attrs + handle->ibody_count, |
| handle->count - handle->ibody_count, start, |
| storage_size, start - block_buf, 1); |
| if (err) |
| goto out2; |
| |
| /* Write a header on the EA block */ |
| header = (struct ext2_ext_attr_header *) block_buf; |
| header->h_magic = EXT2_EXT_ATTR_MAGIC; |
| header->h_refcount = 1; |
| header->h_blocks = 1; |
| |
| /* Get a new block for writing */ |
| err = prep_ea_block_for_write(fs, handle->ino, inode); |
| if (err) |
| goto out2; |
| |
| /* Finally, write the new EA block */ |
| blk = ext2fs_file_acl_block(fs, EXT2_INODE(inode)); |
| err = ext2fs_write_ext_attr3(fs, blk, block_buf, handle->ino); |
| if (err) |
| goto out2; |
| |
| skip_ea_block: |
| blk = ext2fs_file_acl_block(fs, (struct ext2_inode *)inode); |
| if (!block_buf && blk) { |
| /* xattrs shrunk, free the block */ |
| err = ext2fs_free_ext_attr(fs, handle->ino, inode); |
| if (err) |
| goto out; |
| } |
| |
| /* Write the inode */ |
| err = ext2fs_write_inode_full(fs, handle->ino, EXT2_INODE(inode), |
| inode_size); |
| if (err) |
| goto out2; |
| |
| out2: |
| ext2fs_free_mem(&block_buf); |
| out: |
| ext2fs_free_mem(&inode); |
| return err; |
| } |
| |
| static errcode_t read_xattrs_from_buffer(struct ext2_xattr_handle *handle, |
| struct ext2_inode_large *inode, |
| struct ext2_ext_attr_entry *entries, |
| unsigned int storage_size, |
| char *value_start) |
| { |
| struct ext2_xattr *x; |
| struct ext2_ext_attr_entry *entry, *end; |
| const char *prefix; |
| unsigned int remain, prefix_len; |
| errcode_t err; |
| unsigned int values_size = storage_size + |
| ((char *)entries - value_start); |
| |
| /* find the end */ |
| end = entries; |
| remain = storage_size; |
| while (remain >= sizeof(struct ext2_ext_attr_entry) && |
| !EXT2_EXT_IS_LAST_ENTRY(end)) { |
| |
| /* header eats this space */ |
| remain -= sizeof(struct ext2_ext_attr_entry); |
| |
| /* is attribute name valid? */ |
| if (EXT2_EXT_ATTR_SIZE(end->e_name_len) > remain) |
| return EXT2_ET_EA_BAD_NAME_LEN; |
| |
| /* attribute len eats this space */ |
| remain -= EXT2_EXT_ATTR_SIZE(end->e_name_len); |
| end = EXT2_EXT_ATTR_NEXT(end); |
| } |
| |
| entry = entries; |
| remain = storage_size; |
| while (remain >= sizeof(struct ext2_ext_attr_entry) && |
| !EXT2_EXT_IS_LAST_ENTRY(entry)) { |
| |
| /* Allocate space for more attrs? */ |
| if (handle->count == handle->capacity) { |
| err = ext2fs_xattrs_expand(handle, 4); |
| if (err) |
| return err; |
| } |
| |
| x = handle->attrs + handle->count; |
| |
| /* header eats this space */ |
| remain -= sizeof(struct ext2_ext_attr_entry); |
| |
| /* attribute len eats this space */ |
| remain -= EXT2_EXT_ATTR_SIZE(entry->e_name_len); |
| |
| /* Extract name */ |
| prefix = find_ea_prefix(entry->e_name_index); |
| prefix_len = (prefix ? strlen(prefix) : 0); |
| err = ext2fs_get_memzero(entry->e_name_len + prefix_len + 1, |
| &x->name); |
| if (err) |
| return err; |
| if (prefix) |
| memcpy(x->name, prefix, prefix_len); |
| if (entry->e_name_len) |
| memcpy(x->name + prefix_len, |
| (char *)entry + sizeof(*entry), |
| entry->e_name_len); |
| |
| /* Check & copy value */ |
| if (!ext2fs_has_feature_ea_inode(handle->fs->super) && |
| entry->e_value_inum != 0) |
| return EXT2_ET_BAD_EA_BLOCK_NUM; |
| |
| if (entry->e_value_inum == 0) { |
| if (entry->e_value_size > remain) |
| return EXT2_ET_EA_BAD_VALUE_SIZE; |
| |
| if (entry->e_value_offs + entry->e_value_size > values_size) |
| return EXT2_ET_EA_BAD_VALUE_OFFSET; |
| |
| if (entry->e_value_size > 0 && |
| value_start + entry->e_value_offs < |
| (char *)end + sizeof(__u32)) |
| return EXT2_ET_EA_BAD_VALUE_OFFSET; |
| |
| remain -= entry->e_value_size; |
| |
| err = ext2fs_get_mem(entry->e_value_size, &x->value); |
| if (err) |
| return err; |
| memcpy(x->value, value_start + entry->e_value_offs, |
| entry->e_value_size); |
| } else { |
| struct ext2_inode *ea_inode; |
| ext2_file_t ea_file; |
| |
| if (entry->e_value_offs != 0) |
| return EXT2_ET_EA_BAD_VALUE_OFFSET; |
| |
| if (entry->e_value_size > (64 * 1024)) |
| return EXT2_ET_EA_BAD_VALUE_SIZE; |
| |
| err = ext2fs_get_mem(entry->e_value_size, &x->value); |
| if (err) |
| return err; |
| |
| err = ext2fs_file_open(handle->fs, entry->e_value_inum, |
| 0, &ea_file); |
| if (err) |
| return err; |
| |
| ea_inode = ext2fs_file_get_inode(ea_file); |
| if ((ea_inode->i_flags & EXT4_INLINE_DATA_FL) || |
| !(ea_inode->i_flags & EXT4_EA_INODE_FL) || |
| ea_inode->i_links_count == 0) |
| err = EXT2_ET_EA_INODE_CORRUPTED; |
| else if (ext2fs_file_get_size(ea_file) != |
| entry->e_value_size) |
| err = EXT2_ET_EA_BAD_VALUE_SIZE; |
| else |
| err = ext2fs_file_read(ea_file, x->value, |
| entry->e_value_size, 0); |
| ext2fs_file_close(ea_file); |
| if (err) |
| return err; |
| } |
| |
| x->ea_ino = entry->e_value_inum; |
| x->value_len = entry->e_value_size; |
| |
| /* e_hash may be 0 in older inode's ea */ |
| if (entry->e_hash != 0) { |
| __u32 hash; |
| void *data = (entry->e_value_inum != 0) ? |
| 0 : value_start + entry->e_value_offs; |
| |
| err = ext2fs_ext_attr_hash_entry2(handle->fs, entry, |
| data, &hash); |
| if (err) |
| return err; |
| if (entry->e_hash != hash) { |
| struct ext2_inode child; |
| |
| /* Check whether this is an old Lustre-style |
| * ea_inode reference. |
| */ |
| err = ext2fs_read_inode(handle->fs, |
| entry->e_value_inum, |
| &child); |
| if (err) |
| return err; |
| if (child.i_mtime != handle->ino || |
| child.i_generation != inode->i_generation) |
| return EXT2_ET_BAD_EA_HASH; |
| } |
| } |
| |
| handle->count++; |
| entry = EXT2_EXT_ATTR_NEXT(entry); |
| } |
| |
| return 0; |
| } |
| |
| static void xattrs_free_keys(struct ext2_xattr_handle *h) |
| { |
| struct ext2_xattr *a = h->attrs; |
| int i; |
| |
| for (i = 0; i < h->capacity; i++) { |
| if (a[i].name) |
| ext2fs_free_mem(&a[i].name); |
| if (a[i].value) |
| ext2fs_free_mem(&a[i].value); |
| } |
| h->count = 0; |
| h->ibody_count = 0; |
| } |
| |
| errcode_t ext2fs_xattrs_read(struct ext2_xattr_handle *handle) |
| { |
| struct ext2_inode_large *inode; |
| struct ext2_ext_attr_header *header; |
| __u32 ea_inode_magic; |
| unsigned int storage_size; |
| char *start, *block_buf = NULL; |
| blk64_t blk; |
| size_t i; |
| errcode_t err; |
| |
| EXT2_CHECK_MAGIC(handle, EXT2_ET_MAGIC_EA_HANDLE); |
| i = EXT2_INODE_SIZE(handle->fs->super); |
| if (i < sizeof(*inode)) |
| i = sizeof(*inode); |
| err = ext2fs_get_memzero(i, &inode); |
| if (err) |
| return err; |
| |
| err = ext2fs_read_inode_full(handle->fs, handle->ino, |
| (struct ext2_inode *)inode, |
| EXT2_INODE_SIZE(handle->fs->super)); |
| if (err) |
| goto out; |
| |
| xattrs_free_keys(handle); |
| |
| /* Does the inode have space for EA? */ |
| if (inode->i_extra_isize < sizeof(inode->i_extra_isize) || |
| EXT2_INODE_SIZE(handle->fs->super) <= EXT2_GOOD_OLD_INODE_SIZE + |
| inode->i_extra_isize + |
| sizeof(__u32)) |
| goto read_ea_block; |
| if (inode->i_extra_isize & 3) { |
| err = EXT2_ET_INODE_CORRUPTED; |
| goto out; |
| } |
| |
| /* Look for EA in the inode */ |
| memcpy(&ea_inode_magic, ((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + |
| inode->i_extra_isize, sizeof(__u32)); |
| if (ea_inode_magic == EXT2_EXT_ATTR_MAGIC) { |
| storage_size = EXT2_INODE_SIZE(handle->fs->super) - |
| EXT2_GOOD_OLD_INODE_SIZE - inode->i_extra_isize - |
| sizeof(__u32); |
| start = ((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + |
| inode->i_extra_isize + sizeof(__u32); |
| |
| err = read_xattrs_from_buffer(handle, inode, |
| (struct ext2_ext_attr_entry *) start, |
| storage_size, start); |
| if (err) |
| goto out; |
| |
| handle->ibody_count = handle->count; |
| } |
| |
| read_ea_block: |
| /* Look for EA in a separate EA block */ |
| blk = ext2fs_file_acl_block(handle->fs, (struct ext2_inode *)inode); |
| if (blk != 0) { |
| if ((blk < handle->fs->super->s_first_data_block) || |
| (blk >= ext2fs_blocks_count(handle->fs->super))) { |
| err = EXT2_ET_BAD_EA_BLOCK_NUM; |
| goto out; |
| } |
| |
| err = ext2fs_get_mem(handle->fs->blocksize, &block_buf); |
| if (err) |
| goto out; |
| |
| err = ext2fs_read_ext_attr3(handle->fs, blk, block_buf, |
| handle->ino); |
| if (err) |
| goto out3; |
| |
| /* We only know how to deal with v2 EA blocks */ |
| header = (struct ext2_ext_attr_header *) block_buf; |
| if (header->h_magic != EXT2_EXT_ATTR_MAGIC) { |
| err = EXT2_ET_BAD_EA_HEADER; |
| goto out3; |
| } |
| |
| /* Read EAs */ |
| storage_size = handle->fs->blocksize - |
| sizeof(struct ext2_ext_attr_header); |
| start = block_buf + sizeof(struct ext2_ext_attr_header); |
| err = read_xattrs_from_buffer(handle, inode, |
| (struct ext2_ext_attr_entry *) start, |
| storage_size, block_buf); |
| if (err) |
| goto out3; |
| |
| ext2fs_free_mem(&block_buf); |
| } |
| |
| ext2fs_free_mem(&block_buf); |
| ext2fs_free_mem(&inode); |
| return 0; |
| |
| out3: |
| ext2fs_free_mem(&block_buf); |
| out: |
| ext2fs_free_mem(&inode); |
| return err; |
| } |
| |
| errcode_t ext2fs_xattrs_iterate(struct ext2_xattr_handle *h, |
| int (*func)(char *name, char *value, |
| size_t value_len, void *data), |
| void *data) |
| { |
| struct ext2_xattr *x; |
| int dirty = 0; |
| int ret; |
| |
| EXT2_CHECK_MAGIC(h, EXT2_ET_MAGIC_EA_HANDLE); |
| for (x = h->attrs; x < h->attrs + h->count; x++) { |
| ret = func(x->name, x->value, x->value_len, data); |
| if (ret & XATTR_CHANGED) |
| dirty = 1; |
| if (ret & XATTR_ABORT) |
| break; |
| } |
| |
| if (dirty) |
| return ext2fs_xattrs_write(h); |
| return 0; |
| } |
| |
| errcode_t ext2fs_xattr_get(struct ext2_xattr_handle *h, const char *key, |
| void **value, size_t *value_len) |
| { |
| struct ext2_xattr *x; |
| char *val; |
| errcode_t err; |
| |
| EXT2_CHECK_MAGIC(h, EXT2_ET_MAGIC_EA_HANDLE); |
| for (x = h->attrs; x < h->attrs + h->count; x++) { |
| if (strcmp(x->name, key)) |
| continue; |
| |
| if (!(h->flags & XATTR_HANDLE_FLAG_RAW) && |
| ((strcmp(key, "system.posix_acl_default") == 0) || |
| (strcmp(key, "system.posix_acl_access") == 0))) { |
| err = convert_disk_buffer_to_posix_acl(x->value, x->value_len, |
| value, value_len); |
| return err; |
| } else { |
| err = ext2fs_get_mem(x->value_len, &val); |
| if (err) |
| return err; |
| memcpy(val, x->value, x->value_len); |
| *value = val; |
| *value_len = x->value_len; |
| return 0; |
| } |
| } |
| |
| return EXT2_ET_EA_KEY_NOT_FOUND; |
| } |
| |
| errcode_t ext2fs_xattr_inode_max_size(ext2_filsys fs, ext2_ino_t ino, |
| size_t *size) |
| { |
| struct ext2_ext_attr_entry *entry; |
| struct ext2_inode_large *inode; |
| __u32 ea_inode_magic; |
| unsigned int minoff; |
| char *start; |
| size_t i; |
| errcode_t err; |
| |
| i = EXT2_INODE_SIZE(fs->super); |
| if (i < sizeof(*inode)) |
| i = sizeof(*inode); |
| err = ext2fs_get_memzero(i, &inode); |
| if (err) |
| return err; |
| |
| err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)inode, |
| EXT2_INODE_SIZE(fs->super)); |
| if (err) |
| goto out; |
| |
| /* Does the inode have size for EA? */ |
| if (EXT2_INODE_SIZE(fs->super) <= EXT2_GOOD_OLD_INODE_SIZE + |
| inode->i_extra_isize + |
| sizeof(__u32)) { |
| err = EXT2_ET_INLINE_DATA_NO_SPACE; |
| goto out; |
| } |
| |
| minoff = EXT2_INODE_SIZE(fs->super) - sizeof(*inode) - sizeof(__u32); |
| memcpy(&ea_inode_magic, ((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + |
| inode->i_extra_isize, sizeof(__u32)); |
| if (ea_inode_magic == EXT2_EXT_ATTR_MAGIC) { |
| /* has xattrs. calculate the size */ |
| start= ((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + |
| inode->i_extra_isize + sizeof(__u32); |
| entry = (struct ext2_ext_attr_entry *) start; |
| while (!EXT2_EXT_IS_LAST_ENTRY(entry)) { |
| if (!entry->e_value_inum && entry->e_value_size) { |
| unsigned int offs = entry->e_value_offs; |
| if (offs < minoff) |
| minoff = offs; |
| } |
| entry = EXT2_EXT_ATTR_NEXT(entry); |
| } |
| *size = minoff - ((char *)entry - (char *)start) - sizeof(__u32); |
| } else { |
| /* no xattr. return a maximum size */ |
| *size = EXT2_EXT_ATTR_SIZE(minoff - |
| EXT2_EXT_ATTR_LEN(strlen("data")) - |
| EXT2_EXT_ATTR_ROUND - sizeof(__u32)); |
| } |
| |
| out: |
| ext2fs_free_mem(&inode); |
| return err; |
| } |
| |
| static errcode_t xattr_create_ea_inode(ext2_filsys fs, const void *value, |
| size_t value_len, ext2_ino_t *ea_ino) |
| { |
| struct ext2_inode inode; |
| ext2_ino_t ino; |
| ext2_file_t file; |
| __u32 hash; |
| errcode_t ret; |
| |
| ret = ext2fs_new_inode(fs, 0, 0, 0, &ino); |
| if (ret) |
| return ret; |
| |
| memset(&inode, 0, sizeof(inode)); |
| inode.i_flags |= EXT4_EA_INODE_FL; |
| if (ext2fs_has_feature_extents(fs->super)) |
| inode.i_flags |= EXT4_EXTENTS_FL; |
| inode.i_size = 0; |
| inode.i_mode = LINUX_S_IFREG | 0600; |
| inode.i_links_count = 1; |
| ret = ext2fs_write_new_inode(fs, ino, &inode); |
| if (ret) |
| return ret; |
| /* |
| * ref_count and hash utilize inode's i_*time fields. |
| * ext2fs_write_new_inode() call above initializes these fields with |
| * current time. That's why ref count and hash updates are done |
| * separately below. |
| */ |
| ext2fs_set_ea_inode_ref(&inode, 1); |
| hash = ext2fs_crc32c_le(fs->csum_seed, value, value_len); |
| ext2fs_set_ea_inode_hash(&inode, hash); |
| |
| ret = ext2fs_write_inode(fs, ino, &inode); |
| if (ret) |
| return ret; |
| |
| ret = ext2fs_file_open(fs, ino, EXT2_FILE_WRITE, &file); |
| if (ret) |
| return ret; |
| ret = ext2fs_file_write(file, value, value_len, NULL); |
| ext2fs_file_close(file); |
| if (ret) |
| return ret; |
| |
| ext2fs_inode_alloc_stats2(fs, ino, 1 /* inuse */, 0 /* isdir */); |
| |
| *ea_ino = ino; |
| return 0; |
| } |
| |
| static errcode_t xattr_inode_dec_ref(ext2_filsys fs, ext2_ino_t ino) |
| { |
| struct ext2_inode_large inode; |
| __u64 ref_count; |
| errcode_t ret; |
| |
| ret = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode, |
| sizeof(inode)); |
| if (ret) |
| goto out; |
| |
| ref_count = ext2fs_get_ea_inode_ref(EXT2_INODE(&inode)); |
| ref_count--; |
| ext2fs_set_ea_inode_ref(EXT2_INODE(&inode), ref_count); |
| |
| if (ref_count) |
| goto write_out; |
| |
| inode.i_links_count = 0; |
| inode.i_dtime = fs->now ? fs->now : time(0); |
| |
| ret = ext2fs_free_ext_attr(fs, ino, &inode); |
| if (ret) |
| goto write_out; |
| |
| if (ext2fs_inode_has_valid_blocks2(fs, (struct ext2_inode *)&inode)) { |
| ret = ext2fs_punch(fs, ino, (struct ext2_inode *)&inode, NULL, |
| 0, ~0ULL); |
| if (ret) |
| goto out; |
| } |
| |
| ext2fs_inode_alloc_stats2(fs, ino, -1 /* inuse */, 0 /* is_dir */); |
| |
| write_out: |
| ret = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode, |
| sizeof(inode)); |
| out: |
| return ret; |
| } |
| |
| static errcode_t xattr_update_entry(ext2_filsys fs, struct ext2_xattr *x, |
| const char *name, const void *value, |
| size_t value_len, int in_inode) |
| { |
| ext2_ino_t ea_ino = 0; |
| void *new_value = NULL; |
| char *new_name = NULL; |
| int name_len; |
| errcode_t ret; |
| |
| if (!x->name) { |
| name_len = strlen(name); |
| ret = ext2fs_get_mem(name_len + 1, &new_name); |
| if (ret) |
| goto fail; |
| memcpy(new_name, name, name_len + 1); |
| } |
| |
| ret = ext2fs_get_mem(value_len, &new_value); |
| if (ret) |
| goto fail; |
| memcpy(new_value, value, value_len); |
| |
| if (in_inode) { |
| ret = xattr_create_ea_inode(fs, value, value_len, &ea_ino); |
| if (ret) |
| goto fail; |
| } |
| |
| if (x->ea_ino) { |
| ret = xattr_inode_dec_ref(fs, x->ea_ino); |
| if (ret) |
| goto fail; |
| } |
| |
| if (!x->name) |
| x->name = new_name; |
| |
| if (x->value) |
| ext2fs_free_mem(&x->value); |
| x->value = new_value; |
| x->value_len = value_len; |
| x->ea_ino = ea_ino; |
| return 0; |
| fail: |
| if (new_name) |
| ext2fs_free_mem(&new_name); |
| if (new_value) |
| ext2fs_free_mem(&new_value); |
| if (ea_ino) |
| xattr_inode_dec_ref(fs, ea_ino); |
| return ret; |
| } |
| |
| static int xattr_find_position(struct ext2_xattr *attrs, int count, |
| const char *name) |
| { |
| struct ext2_xattr *x; |
| int i; |
| const char *shortname, *x_shortname; |
| int name_idx, x_name_idx; |
| int shortname_len, x_shortname_len; |
| |
| find_ea_index(name, &shortname, &name_idx); |
| shortname_len = strlen(shortname); |
| |
| for (i = 0, x = attrs; i < count; i++, x++) { |
| find_ea_index(x->name, &x_shortname, &x_name_idx); |
| if (name_idx < x_name_idx) |
| break; |
| if (name_idx > x_name_idx) |
| continue; |
| |
| x_shortname_len = strlen(x_shortname); |
| if (shortname_len < x_shortname_len) |
| break; |
| if (shortname_len > x_shortname_len) |
| continue; |
| |
| if (memcmp(shortname, x_shortname, shortname_len) <= 0) |
| break; |
| } |
| return i; |
| } |
| |
| static errcode_t xattr_array_update(struct ext2_xattr_handle *h, |
| const char *name, |
| const void *value, size_t value_len, |
| int ibody_free, int block_free, |
| int old_idx, int in_inode) |
| { |
| struct ext2_xattr tmp; |
| int add_to_ibody; |
| int needed; |
| int name_len, name_idx; |
| const char *shortname; |
| int new_idx; |
| int ret; |
| |
| find_ea_index(name, &shortname, &name_idx); |
| name_len = strlen(shortname); |
| |
| needed = EXT2_EXT_ATTR_LEN(name_len); |
| if (!in_inode) |
| needed += EXT2_EXT_ATTR_SIZE(value_len); |
| |
| if (old_idx >= 0 && old_idx < h->ibody_count) { |
| ibody_free += EXT2_EXT_ATTR_LEN(name_len); |
| if (!h->attrs[old_idx].ea_ino) |
| ibody_free += EXT2_EXT_ATTR_SIZE( |
| h->attrs[old_idx].value_len); |
| } |
| |
| if (needed <= ibody_free) { |
| if (old_idx < 0) { |
| new_idx = h->ibody_count; |
| add_to_ibody = 1; |
| goto add_new; |
| } |
| |
| /* Update the existing entry. */ |
| ret = xattr_update_entry(h->fs, &h->attrs[old_idx], name, |
| value, value_len, in_inode); |
| if (ret) |
| return ret; |
| if (h->ibody_count <= old_idx) { |
| /* Move entry from block to the end of ibody. */ |
| tmp = h->attrs[old_idx]; |
| memmove(h->attrs + h->ibody_count + 1, |
| h->attrs + h->ibody_count, |
| (old_idx - h->ibody_count) * sizeof(*h->attrs)); |
| h->attrs[h->ibody_count] = tmp; |
| h->ibody_count++; |
| } |
| return 0; |
| } |
| |
| if (h->ibody_count <= old_idx) { |
| block_free += EXT2_EXT_ATTR_LEN(name_len); |
| if (!h->attrs[old_idx].ea_ino) |
| block_free += |
| EXT2_EXT_ATTR_SIZE(h->attrs[old_idx].value_len); |
| } |
| |
| if (needed > block_free) |
| return EXT2_ET_EA_NO_SPACE; |
| |
| if (old_idx >= 0) { |
| /* Update the existing entry. */ |
| ret = xattr_update_entry(h->fs, &h->attrs[old_idx], name, |
| value, value_len, in_inode); |
| if (ret) |
| return ret; |
| if (old_idx < h->ibody_count) { |
| /* |
| * Move entry from ibody to the block. Note that |
| * entries in the block are sorted. |
| */ |
| new_idx = xattr_find_position(h->attrs + h->ibody_count, |
| h->count - h->ibody_count, name); |
| new_idx += h->ibody_count - 1; |
| tmp = h->attrs[old_idx]; |
| memmove(h->attrs + old_idx, h->attrs + old_idx + 1, |
| (new_idx - old_idx) * sizeof(*h->attrs)); |
| h->attrs[new_idx] = tmp; |
| h->ibody_count--; |
| } |
| return 0; |
| } |
| |
| new_idx = xattr_find_position(h->attrs + h->ibody_count, |
| h->count - h->ibody_count, name); |
| new_idx += h->ibody_count; |
| add_to_ibody = 0; |
| |
| add_new: |
| if (h->count == h->capacity) { |
| ret = ext2fs_xattrs_expand(h, 4); |
| if (ret) |
| return ret; |
| } |
| |
| ret = xattr_update_entry(h->fs, &h->attrs[h->count], name, value, |
| value_len, in_inode); |
| if (ret) |
| return ret; |
| |
| tmp = h->attrs[h->count]; |
| memmove(h->attrs + new_idx + 1, h->attrs + new_idx, |
| (h->count - new_idx)*sizeof(*h->attrs)); |
| h->attrs[new_idx] = tmp; |
| if (add_to_ibody) |
| h->ibody_count++; |
| h->count++; |
| return 0; |
| } |
| |
| static int space_used(struct ext2_xattr *attrs, int count) |
| { |
| int total = 0; |
| struct ext2_xattr *x; |
| const char *shortname; |
| int i, len, name_idx; |
| |
| for (i = 0, x = attrs; i < count; i++, x++) { |
| find_ea_index(x->name, &shortname, &name_idx); |
| len = strlen(shortname); |
| total += EXT2_EXT_ATTR_LEN(len); |
| if (!x->ea_ino) |
| total += EXT2_EXT_ATTR_SIZE(x->value_len); |
| } |
| return total; |
| } |
| |
| /* |
| * The minimum size of EA value when you start storing it in an external inode |
| * size of block - size of header - size of 1 entry - 4 null bytes |
| */ |
| #define EXT4_XATTR_MIN_LARGE_EA_SIZE(b) \ |
| ((b) - EXT2_EXT_ATTR_LEN(3) - sizeof(struct ext2_ext_attr_header) - 4) |
| |
| errcode_t ext2fs_xattr_set(struct ext2_xattr_handle *h, |
| const char *name, |
| const void *value, |
| size_t value_len) |
| { |
| ext2_filsys fs = h->fs; |
| const int inode_size = EXT2_INODE_SIZE(fs->super); |
| struct ext2_inode_large *inode = NULL; |
| struct ext2_xattr *x; |
| char *new_value; |
| int ibody_free, block_free; |
| int in_inode = 0; |
| int old_idx = -1; |
| int extra_isize; |
| errcode_t ret; |
| |
| EXT2_CHECK_MAGIC(h, EXT2_ET_MAGIC_EA_HANDLE); |
| |
| ret = ext2fs_get_mem(value_len, &new_value); |
| if (ret) |
| return ret; |
| if (!(h->flags & XATTR_HANDLE_FLAG_RAW) && |
| ((strcmp(name, "system.posix_acl_default") == 0) || |
| (strcmp(name, "system.posix_acl_access") == 0))) { |
| ret = convert_posix_acl_to_disk_buffer(value, value_len, |
| new_value, &value_len); |
| if (ret) |
| goto out; |
| } else |
| memcpy(new_value, value, value_len); |
| |
| /* Imitate kernel behavior by skipping update if value is the same. */ |
| for (x = h->attrs; x < h->attrs + h->count; x++) { |
| if (!strcmp(x->name, name)) { |
| if (!x->ea_ino && x->value_len == value_len && |
| !memcmp(x->value, new_value, value_len)) { |
| ret = 0; |
| goto out; |
| } |
| old_idx = x - h->attrs; |
| break; |
| } |
| } |
| |
| ret = ext2fs_get_memzero(inode_size, &inode); |
| if (ret) |
| goto out; |
| ret = ext2fs_read_inode_full(fs, h->ino, |
| (struct ext2_inode *)inode, |
| inode_size); |
| if (ret) |
| goto out; |
| if (inode_size > EXT2_GOOD_OLD_INODE_SIZE) { |
| extra_isize = inode->i_extra_isize; |
| if (extra_isize == 0) { |
| extra_isize = fs->super->s_want_extra_isize; |
| if (extra_isize == 0) |
| extra_isize = sizeof(__u32); |
| } |
| ibody_free = inode_size - EXT2_GOOD_OLD_INODE_SIZE; |
| ibody_free -= extra_isize; |
| /* Extended attribute magic and final null entry. */ |
| ibody_free -= sizeof(__u32) * 2; |
| ibody_free -= space_used(h->attrs, h->ibody_count); |
| } else |
| ibody_free = 0; |
| |
| /* Inline data can only go to ibody. */ |
| if (strcmp(name, "system.data") == 0) { |
| if (h->ibody_count <= old_idx) { |
| ret = EXT2_ET_FILESYSTEM_CORRUPTED; |
| goto out; |
| } |
| ret = xattr_array_update(h, name, value, value_len, ibody_free, |
| 0 /* block_free */, old_idx, |
| 0 /* in_inode */); |
| if (ret) |
| goto out; |
| goto write_out; |
| } |
| |
| block_free = fs->blocksize; |
| block_free -= sizeof(struct ext2_ext_attr_header); |
| /* Final null entry. */ |
| block_free -= sizeof(__u32); |
| block_free -= space_used(h->attrs + h->ibody_count, |
| h->count - h->ibody_count); |
| |
| if (ext2fs_has_feature_ea_inode(fs->super) && |
| value_len > EXT4_XATTR_MIN_LARGE_EA_SIZE(fs->blocksize)) |
| in_inode = 1; |
| |
| ret = xattr_array_update(h, name, value, value_len, ibody_free, |
| block_free, old_idx, in_inode); |
| if (ret == EXT2_ET_EA_NO_SPACE && !in_inode && |
| ext2fs_has_feature_ea_inode(fs->super)) |
| ret = xattr_array_update(h, name, value, value_len, ibody_free, |
| block_free, old_idx, 1 /* in_inode */); |
| if (ret) |
| goto out; |
| |
| write_out: |
| ret = ext2fs_xattrs_write(h); |
| out: |
| if (inode) |
| ext2fs_free_mem(&inode); |
| ext2fs_free_mem(&new_value); |
| return ret; |
| } |
| |
| errcode_t ext2fs_xattr_remove(struct ext2_xattr_handle *handle, |
| const char *key) |
| { |
| struct ext2_xattr *x; |
| struct ext2_xattr *end = handle->attrs + handle->count; |
| |
| EXT2_CHECK_MAGIC(handle, EXT2_ET_MAGIC_EA_HANDLE); |
| for (x = handle->attrs; x < end; x++) { |
| if (strcmp(x->name, key) == 0) { |
| ext2fs_free_mem(&x->name); |
| ext2fs_free_mem(&x->value); |
| if (x->ea_ino) |
| xattr_inode_dec_ref(handle->fs, x->ea_ino); |
| memmove(x, x + 1, (end - x - 1)*sizeof(*x)); |
| memset(end - 1, 0, sizeof(*end)); |
| if (x < handle->attrs + handle->ibody_count) |
| handle->ibody_count--; |
| handle->count--; |
| return ext2fs_xattrs_write(handle); |
| } |
| } |
| |
| /* no key found, success! */ |
| return 0; |
| } |
| |
| errcode_t ext2fs_xattrs_open(ext2_filsys fs, ext2_ino_t ino, |
| struct ext2_xattr_handle **handle) |
| { |
| struct ext2_xattr_handle *h; |
| errcode_t err; |
| |
| if (!ext2fs_has_feature_xattr(fs->super) && |
| !ext2fs_has_feature_inline_data(fs->super)) |
| return EXT2_ET_MISSING_EA_FEATURE; |
| |
| err = ext2fs_get_memzero(sizeof(*h), &h); |
| if (err) |
| return err; |
| |
| h->magic = EXT2_ET_MAGIC_EA_HANDLE; |
| h->capacity = 4; |
| err = ext2fs_get_arrayzero(h->capacity, sizeof(struct ext2_xattr), |
| &h->attrs); |
| if (err) { |
| ext2fs_free_mem(&h); |
| return err; |
| } |
| h->count = 0; |
| h->ino = ino; |
| h->fs = fs; |
| *handle = h; |
| return 0; |
| } |
| |
| errcode_t ext2fs_xattrs_close(struct ext2_xattr_handle **handle) |
| { |
| struct ext2_xattr_handle *h = *handle; |
| |
| EXT2_CHECK_MAGIC(h, EXT2_ET_MAGIC_EA_HANDLE); |
| xattrs_free_keys(h); |
| ext2fs_free_mem(&h->attrs); |
| ext2fs_free_mem(handle); |
| return 0; |
| } |
| |
| errcode_t ext2fs_xattrs_count(struct ext2_xattr_handle *handle, size_t *count) |
| { |
| EXT2_CHECK_MAGIC(handle, EXT2_ET_MAGIC_EA_HANDLE); |
| *count = handle->count; |
| return 0; |
| } |
| |
| errcode_t ext2fs_xattrs_flags(struct ext2_xattr_handle *handle, |
| unsigned int *new_flags, unsigned int *old_flags) |
| { |
| EXT2_CHECK_MAGIC(handle, EXT2_ET_MAGIC_EA_HANDLE); |
| if (old_flags) |
| *old_flags = handle->flags; |
| if (new_flags) |
| handle->flags = *new_flags; |
| return 0; |
| } |