| /* |
| * 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 "ext2fs.h" |
| |
| #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_block == 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; |
| } |
| |
| 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; |
| } |
| |
| #undef NAME_HASH_SHIFT |
| #undef VALUE_HASH_SHIFT |
| |
| 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; |
| size_t value_len; |
| }; |
| |
| struct ext2_xattr_handle { |
| errcode_t magic; |
| ext2_filsys fs; |
| struct ext2_xattr *attrs; |
| size_t length, count; |
| ext2_ino_t ino; |
| int dirty; |
| }; |
| |
| 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->length + expandby, |
| sizeof(struct ext2_xattr), &new_attrs); |
| if (err) |
| return err; |
| |
| memcpy(new_attrs, h->attrs, h->length * sizeof(struct ext2_xattr)); |
| ext2fs_free_mem(&h->attrs); |
| h->length += 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 int find_ea_index(char *fullname, char **name, int *index); |
| |
| /* Push empty attributes to the end and inlinedata to the front. */ |
| static int attr_compare(const void *a, const void *b) |
| { |
| const struct ext2_xattr *xa = a, *xb = b; |
| char *xa_suffix, *xb_suffix; |
| int xa_idx, xb_idx; |
| int cmp; |
| |
| if (xa->name == NULL) |
| return +1; |
| else if (xb->name == NULL) |
| return -1; |
| else if (!strcmp(xa->name, "system.data")) |
| return -1; |
| else if (!strcmp(xb->name, "system.data")) |
| return +1; |
| |
| /* |
| * Duplicate the kernel's sorting algorithm because xattr blocks |
| * require sorted keys. |
| */ |
| xa_suffix = xa->name; |
| xb_suffix = xb->name; |
| xa_idx = xb_idx = 0; |
| find_ea_index(xa->name, &xa_suffix, &xa_idx); |
| find_ea_index(xb->name, &xb_suffix, &xb_idx); |
| cmp = xa_idx - xb_idx; |
| if (cmp) |
| return cmp; |
| cmp = strlen(xa_suffix) - strlen(xb_suffix); |
| if (cmp) |
| return cmp; |
| cmp = strcmp(xa_suffix, xb_suffix); |
| return cmp; |
| } |
| |
| 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(char *fullname, char **name, int *index) |
| { |
| struct ea_name_index *e; |
| |
| for (e = ea_names; e->name; e++) { |
| if (memcmp(fullname, e->name, strlen(e->name)) == 0) { |
| *name = (char *)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 errcode_t write_xattrs_to_buffer(struct ext2_xattr_handle *handle, |
| struct ext2_xattr **pos, |
| void *entries_start, |
| unsigned int storage_size, |
| unsigned int value_offset_correction, |
| int write_hash) |
| { |
| struct ext2_xattr *x = *pos; |
| struct ext2_ext_attr_entry *e = entries_start; |
| char *end = (char *) entries_start + storage_size; |
| char *shortname; |
| unsigned int entry_size, value_size; |
| int idx, ret; |
| |
| memset(entries_start, 0, storage_size); |
| /* For all remaining x... */ |
| for (; x < handle->attrs + handle->length; x++) { |
| if (!x->name) |
| continue; |
| |
| /* Calculate index and shortname position */ |
| shortname = x->name; |
| ret = find_ea_index(x->name, &shortname, &idx); |
| |
| /* Calculate entry and value size */ |
| entry_size = (sizeof(*e) + strlen(shortname) + |
| EXT2_EXT_ATTR_PAD - 1) & |
| ~(EXT2_EXT_ATTR_PAD - 1); |
| value_size = ((x->value_len + EXT2_EXT_ATTR_PAD - 1) / |
| EXT2_EXT_ATTR_PAD) * EXT2_EXT_ATTR_PAD; |
| |
| /* |
| * Would entry collide with value? |
| * Note that we must leave sufficient room for a (u32)0 to |
| * mark the end of the entries. |
| */ |
| if ((char *)e + entry_size + sizeof(__u32) > end - value_size) |
| break; |
| |
| /* Fill out e appropriately */ |
| e->e_name_len = strlen(shortname); |
| e->e_name_index = (ret ? idx : 0); |
| e->e_value_offs = end - value_size - (char *)entries_start + |
| value_offset_correction; |
| e->e_value_block = 0; |
| e->e_value_size = x->value_len; |
| |
| /* Store name and value */ |
| end -= value_size; |
| memcpy((char *)e + sizeof(*e), shortname, e->e_name_len); |
| memcpy(end, x->value, e->e_value_size); |
| |
| if (write_hash) |
| e->e_hash = ext2fs_ext_attr_hash_entry(e, end); |
| else |
| e->e_hash = 0; |
| |
| e = EXT2_EXT_ATTR_NEXT(e); |
| *(__u32 *)e = 0; |
| } |
| *pos = x; |
| |
| return 0; |
| } |
| |
| errcode_t ext2fs_xattrs_write(struct ext2_xattr_handle *handle) |
| { |
| struct ext2_xattr *x; |
| 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 = 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; |
| |
| /* If extra_isize isn't set, we need to set it now */ |
| if (inode->i_extra_isize == 0 && |
| EXT2_INODE_SIZE(handle->fs->super) > EXT2_GOOD_OLD_INODE_SIZE) { |
| char *p = (char *)inode; |
| size_t extra = handle->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; |
| } |
| |
| /* |
| * Force the inlinedata attr to the front and the empty entries |
| * to the end. |
| */ |
| x = handle->attrs; |
| qsort(x, handle->length, sizeof(struct ext2_xattr), attr_compare); |
| |
| /* 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 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 = 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 = write_xattrs_to_buffer(handle, &x, start, storage_size, 0, 0); |
| if (err) |
| goto out; |
| |
| write_ea_block: |
| /* Are we done? */ |
| if (x >= handle->attrs + handle->count) |
| goto skip_ea_block; |
| |
| /* Write the EA block */ |
| err = ext2fs_get_memzero(handle->fs->blocksize, &block_buf); |
| if (err) |
| goto out; |
| |
| storage_size = handle->fs->blocksize - |
| sizeof(struct ext2_ext_attr_header); |
| start = block_buf + sizeof(struct ext2_ext_attr_header); |
| |
| err = write_xattrs_to_buffer(handle, &x, start, storage_size, |
| start - block_buf, 1); |
| if (err) |
| goto out2; |
| |
| if (x < handle->attrs + handle->length) { |
| err = EXT2_ET_EA_NO_SPACE; |
| 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(handle->fs, handle->ino, inode); |
| if (err) |
| goto out2; |
| |
| /* Finally, write the new EA block */ |
| blk = ext2fs_file_acl_block(handle->fs, |
| (struct ext2_inode *)inode); |
| err = ext2fs_write_ext_attr3(handle->fs, blk, block_buf, |
| handle->ino); |
| if (err) |
| goto out2; |
| |
| skip_ea_block: |
| blk = ext2fs_file_acl_block(handle->fs, (struct ext2_inode *)inode); |
| if (!block_buf && blk) { |
| /* xattrs shrunk, free the block */ |
| err = ext2fs_free_ext_attr(handle->fs, handle->ino, inode); |
| if (err) |
| goto out; |
| } |
| |
| /* Write the inode */ |
| err = ext2fs_write_inode_full(handle->fs, handle->ino, |
| (struct ext2_inode *)inode, |
| EXT2_INODE_SIZE(handle->fs->super)); |
| if (err) |
| goto out2; |
| |
| out2: |
| ext2fs_free_mem(&block_buf); |
| out: |
| ext2fs_free_mem(&inode); |
| handle->dirty = 0; |
| return err; |
| } |
| |
| static errcode_t read_xattrs_from_buffer(struct ext2_xattr_handle *handle, |
| struct ext2_ext_attr_entry *entries, |
| unsigned int storage_size, |
| char *value_start, |
| size_t *nr_read) |
| { |
| 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); |
| |
| x = handle->attrs; |
| while (x->name) |
| x++; |
| |
| /* 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)) { |
| __u32 hash; |
| |
| /* 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); |
| |
| /* check value size */ |
| 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; |
| |
| /* e_value_block must be 0 in inode's ea */ |
| if (entry->e_value_block != 0) |
| return EXT2_ET_BAD_EA_BLOCK_NUM; |
| |
| hash = ext2fs_ext_attr_hash_entry(entry, value_start + |
| entry->e_value_offs); |
| |
| /* e_hash may be 0 in older inode's ea */ |
| if (entry->e_hash != 0 && entry->e_hash != hash) |
| return EXT2_ET_BAD_EA_HASH; |
| |
| remain -= entry->e_value_size; |
| |
| /* Allocate space for more attrs? */ |
| if (x == handle->attrs + handle->length) { |
| err = ext2fs_xattrs_expand(handle, 4); |
| if (err) |
| return err; |
| x = handle->attrs + handle->length - 4; |
| } |
| |
| /* Extract name/value */ |
| 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); |
| |
| err = ext2fs_get_mem(entry->e_value_size, &x->value); |
| if (err) |
| return err; |
| x->value_len = entry->e_value_size; |
| memcpy(x->value, value_start + entry->e_value_offs, |
| entry->e_value_size); |
| x++; |
| (*nr_read)++; |
| entry = EXT2_EXT_ATTR_NEXT(entry); |
| } |
| |
| return 0; |
| } |
| |
| static void xattrs_free_keys(struct ext2_xattr_handle *h) |
| { |
| struct ext2_xattr *a = h->attrs; |
| size_t i; |
| |
| for (i = 0; i < h->length; i++) { |
| if (a[i].name) |
| ext2fs_free_mem(&a[i].name); |
| if (a[i].value) |
| ext2fs_free_mem(&a[i].value); |
| } |
| h->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, |
| (struct ext2_ext_attr_entry *) start, storage_size, |
| start, &handle->count); |
| if (err) |
| goto out; |
| } |
| |
| 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, |
| (struct ext2_ext_attr_entry *) start, storage_size, |
| block_buf, &handle->count); |
| 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 ret; |
| |
| EXT2_CHECK_MAGIC(h, EXT2_ET_MAGIC_EA_HANDLE); |
| for (x = h->attrs; x < h->attrs + h->length; x++) { |
| if (!x->name) |
| continue; |
| |
| ret = func(x->name, x->value, x->value_len, data); |
| if (ret & XATTR_CHANGED) |
| h->dirty = 1; |
| if (ret & XATTR_ABORT) |
| return 0; |
| } |
| |
| 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->length; x++) { |
| if (!x->name) |
| continue; |
| |
| if (strcmp(x->name, key) == 0) { |
| 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_block && 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; |
| } |
| |
| errcode_t ext2fs_xattr_set(struct ext2_xattr_handle *handle, |
| const char *key, |
| const void *value, |
| size_t value_len) |
| { |
| struct ext2_xattr *x, *last_empty; |
| char *new_value; |
| errcode_t err; |
| |
| EXT2_CHECK_MAGIC(handle, EXT2_ET_MAGIC_EA_HANDLE); |
| last_empty = NULL; |
| for (x = handle->attrs; x < handle->attrs + handle->length; x++) { |
| if (!x->name) { |
| last_empty = x; |
| continue; |
| } |
| |
| /* Replace xattr */ |
| if (strcmp(x->name, key) == 0) { |
| err = ext2fs_get_mem(value_len, &new_value); |
| if (err) |
| return err; |
| memcpy(new_value, value, value_len); |
| ext2fs_free_mem(&x->value); |
| x->value = new_value; |
| x->value_len = value_len; |
| handle->dirty = 1; |
| return 0; |
| } |
| } |
| |
| /* Add attr to empty slot */ |
| if (last_empty) { |
| err = ext2fs_get_mem(strlen(key) + 1, &last_empty->name); |
| if (err) |
| return err; |
| strcpy(last_empty->name, key); |
| |
| err = ext2fs_get_mem(value_len, &last_empty->value); |
| if (err) |
| return err; |
| memcpy(last_empty->value, value, value_len); |
| last_empty->value_len = value_len; |
| handle->dirty = 1; |
| handle->count++; |
| return 0; |
| } |
| |
| /* Expand array, append slot */ |
| err = ext2fs_xattrs_expand(handle, 4); |
| if (err) |
| return err; |
| |
| x = handle->attrs + handle->length - 4; |
| err = ext2fs_get_mem(strlen(key) + 1, &x->name); |
| if (err) |
| return err; |
| strcpy(x->name, key); |
| |
| err = ext2fs_get_mem(value_len, &x->value); |
| if (err) |
| return err; |
| memcpy(x->value, value, value_len); |
| x->value_len = value_len; |
| handle->dirty = 1; |
| handle->count++; |
| return 0; |
| } |
| |
| errcode_t ext2fs_xattr_remove(struct ext2_xattr_handle *handle, |
| const char *key) |
| { |
| struct ext2_xattr *x; |
| |
| EXT2_CHECK_MAGIC(handle, EXT2_ET_MAGIC_EA_HANDLE); |
| for (x = handle->attrs; x < handle->attrs + handle->length; x++) { |
| if (!x->name) |
| continue; |
| |
| if (strcmp(x->name, key) == 0) { |
| ext2fs_free_mem(&x->name); |
| ext2fs_free_mem(&x->value); |
| x->value_len = 0; |
| handle->dirty = 1; |
| handle->count--; |
| return 0; |
| } |
| } |
| |
| /* 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->length = 4; |
| err = ext2fs_get_arrayzero(h->length, 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; |
| errcode_t err; |
| |
| EXT2_CHECK_MAGIC(h, EXT2_ET_MAGIC_EA_HANDLE); |
| if (h->dirty) { |
| err = ext2fs_xattrs_write(h); |
| if (err) |
| return err; |
| } |
| |
| 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; |
| } |