X-Git-Url: https://git.whamcloud.com/?a=blobdiff_plain;f=lib%2Fext2fs%2Fext_attr.c;h=ad513715e9bf3b8149f4750e37239135f30fe5e8;hb=b4450ead;hp=0a4f8c0e0611596161a4a9c6cda37dc642d8929f;hpb=77b3e9871877fce9908b0696250787fd6ea61af9;p=tools%2Fe2fsprogs.git diff --git a/lib/ext2fs/ext_attr.c b/lib/ext2fs/ext_attr.c index 0a4f8c0..ad51371 100644 --- a/lib/ext2fs/ext_attr.c +++ b/lib/ext2fs/ext_attr.c @@ -18,12 +18,31 @@ #endif #include #include +#include +#if defined HAVE_SYS_XATTR_H +#include +#elif defined HAVE_ATTR_XATTR_H +#include +#endif #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 @@ -45,7 +64,7 @@ __u32 ext2fs_ext_attr_hash_entry(struct ext2_ext_attr_entry *entry, void *data) } /* The hash needs to be calculated on the data in little-endian. */ - if (entry->e_value_block == 0 && entry->e_value_size != 0) { + 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--) { @@ -58,6 +77,112 @@ __u32 ext2fs_ext_attr_hash_entry(struct ext2_ext_attr_entry *entry, void *data) 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; +} + +/* + * Re-compute the extended attribute hash value after an entry has changed. + */ +static void ext2fs_attr_rehash(struct ext2_ext_attr_header *header, + struct ext2_ext_attr_entry *entry) +{ + struct ext2_ext_attr_entry *here; + __u32 hash = 0; + + entry->e_hash = ext2fs_ext_attr_hash_entry(entry, (char *)header + + entry->e_value_offs); + + here = ENTRY(header+1); + while (!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 && @@ -68,9 +193,6 @@ static errcode_t check_ext_attr_header(struct ext2_ext_attr_header *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) { @@ -171,7 +293,10 @@ errcode_t ext2fs_adjust_ea_refcount3(ext2_filsys fs, blk64_t blk, if (retval) goto errout; - header = (struct ext2_ext_attr_header *) block_buf; + header = BHDR(block_buf); + if (header->h_magic != EXT2_EXT_ATTR_MAGIC) + return EXT2_ET_EA_BAD_MAGIC; + header->h_refcount += adjust; if (newcount) *newcount = header->h_refcount; @@ -204,18 +329,23 @@ errcode_t ext2fs_adjust_ea_refcount(ext2_filsys fs, blk_t blk, /* Manipulate the contents of extended attribute regions */ struct ext2_xattr { + int name_index; char *name; + char *short_name; void *value; - size_t value_len; + unsigned int value_len; + ext2_ino_t ea_ino; }; struct ext2_xattr_handle { errcode_t magic; ext2_filsys fs; struct ext2_xattr *attrs; - size_t length, count; + int capacity; + int count; + int ibody_count; ext2_ino_t ino; - int dirty; + unsigned int flags; }; static errcode_t ext2fs_xattrs_expand(struct ext2_xattr_handle *h, @@ -224,14 +354,14 @@ static errcode_t ext2fs_xattrs_expand(struct ext2_xattr_handle *h, struct ext2_xattr *new_attrs; errcode_t err; - err = ext2fs_get_arrayzero(h->length + expandby, + err = ext2fs_get_arrayzero(h->capacity + expandby, sizeof(struct ext2_xattr), &new_attrs); if (err) return err; - memcpy(new_attrs, h->attrs, h->length * sizeof(struct ext2_xattr)); + memcpy(new_attrs, h->attrs, h->capacity * sizeof(struct ext2_xattr)); ext2fs_free_mem(&h->attrs); - h->length += expandby; + h->capacity += expandby; h->attrs = new_attrs; return 0; @@ -244,32 +374,18 @@ struct ea_name_index { /* Keep these names sorted in order of decreasing specificity. */ static struct ea_name_index ea_names[] = { + {10, "gnu."}, {3, "system.posix_acl_default"}, {2, "system.posix_acl_access"}, {8, "system.richacl"}, {6, "security."}, + {5, "lustre."}, {4, "trusted."}, {7, "system."}, {1, "user."}, {0, NULL}, }; -/* 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; - - 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; - return 0; -} - static const char *find_ea_prefix(int index) { struct ea_name_index *e; @@ -281,13 +397,13 @@ static const char *find_ea_prefix(int index) return NULL; } -static int find_ea_index(char *fullname, char **name, int *index) +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 (memcmp(fullname, e->name, strlen(e->name)) == 0) { - *name = (char *)fullname + strlen(e->name); + if (strncmp(fullname, e->name, strlen(e->name)) == 0) { + *name = fullname + strlen(e->name); *index = e->index; return 1; } @@ -430,74 +546,187 @@ out: } -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) +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; + } + 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 = *pos; + struct ext2_xattr *x; 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; + unsigned int value_size; + errcode_t err; 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); + for (x = attrs; x < attrs + count; x++) { 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; + e->e_name_len = strlen(x->short_name); + e->e_name_index = x->name_index; - /* 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); + e->e_value_size = x->value_len; + e->e_value_inum = x->ea_ino; + + /* Store name */ + memcpy((char *)e + sizeof(*e), x->short_name, 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) - e->e_hash = ext2fs_ext_attr_hash_entry(e, end); - else + 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; } - *pos = x; - return 0; } errcode_t ext2fs_xattrs_write(struct ext2_xattr_handle *handle) { - struct ext2_xattr *x; + 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; @@ -508,83 +737,73 @@ errcode_t ext2fs_xattrs_write(struct ext2_xattr_handle *handle) errcode_t err; EXT2_CHECK_MAGIC(handle, EXT2_ET_MAGIC_EA_HANDLE); - i = EXT2_INODE_SIZE(handle->fs->super); + 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(handle->fs, handle->ino, - (struct ext2_inode *)inode, - EXT2_INODE_SIZE(handle->fs->super)); + 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 && - EXT2_INODE_SIZE(handle->fs->super) > EXT2_GOOD_OLD_INODE_SIZE) { + inode_size > EXT2_GOOD_OLD_INODE_SIZE) { char *p = (char *)inode; - size_t extra = handle->fs->super->s_want_extra_isize; + 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; } - - /* - * 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); + 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) || - EXT2_INODE_SIZE(handle->fs->super) <= EXT2_GOOD_OLD_INODE_SIZE + - inode->i_extra_isize + - sizeof(__u32)) + 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 = EXT2_INODE_SIZE(handle->fs->super) - - EXT2_GOOD_OLD_INODE_SIZE - inode->i_extra_isize - - 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); + inode->i_extra_isize + sizeof(__u32); - err = write_xattrs_to_buffer(handle, &x, start, storage_size, 0, 0); + 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 (x >= handle->attrs + handle->count) + 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(handle->fs->blocksize, &block_buf); + err = ext2fs_get_memzero(fs->blocksize, &block_buf); if (err) goto out; - storage_size = handle->fs->blocksize - - sizeof(struct ext2_ext_attr_header); + storage_size = 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); + 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; - 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; @@ -592,31 +811,28 @@ write_ea_block: header->h_blocks = 1; /* Get a new block for writing */ - err = prep_ea_block_for_write(handle->fs, handle->ino, inode); + 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(handle->fs, - (struct ext2_inode *)inode); - err = ext2fs_write_ext_attr3(handle->fs, blk, block_buf, - handle->ino); + 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(handle->fs, (struct ext2_inode *)inode); + blk = ext2fs_file_acl_block(fs, (struct ext2_inode *)inode); if (!block_buf && blk) { /* xattrs shrunk, free the block */ - err = ext2fs_free_ext_attr(handle->fs, handle->ino, inode); + err = ext2fs_free_ext_attr(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)); + err = ext2fs_write_inode_full(fs, handle->ino, EXT2_INODE(inode), + inode_size); if (err) goto out2; @@ -624,15 +840,14 @@ 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_inode_large *inode, struct ext2_ext_attr_entry *entries, unsigned int storage_size, - char *value_start, - size_t *nr_read) + char *value_start) { struct ext2_xattr *x; struct ext2_ext_attr_entry *entry, *end; @@ -642,10 +857,6 @@ static errcode_t read_xattrs_from_buffer(struct ext2_xattr_handle *handle, 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; @@ -668,48 +879,23 @@ static errcode_t read_xattrs_from_buffer(struct ext2_xattr_handle *handle, 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) { + if (handle->count == handle->capacity) { err = ext2fs_xattrs_expand(handle, 4); if (err) return err; - x = handle->attrs + handle->length - 4; } - /* Extract name/value */ + 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, @@ -722,15 +908,99 @@ static errcode_t read_xattrs_from_buffer(struct ext2_xattr_handle *handle, memcpy(x->name + prefix_len, (char *)entry + sizeof(*entry), entry->e_name_len); + x->short_name = x->name + prefix_len; + x->name_index = entry->e_name_index; - err = ext2fs_get_mem(entry->e_value_size, &x->value); - if (err) - return err; + /* 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 ((__u64) 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; - memcpy(x->value, value_start + entry->e_value_offs, - entry->e_value_size); - x++; - (*nr_read)++; + + /* 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); } @@ -740,41 +1010,31 @@ static errcode_t read_xattrs_from_buffer(struct ext2_xattr_handle *handle, static void xattrs_free_keys(struct ext2_xattr_handle *h) { struct ext2_xattr *a = h->attrs; - size_t i; + int i; - for (i = 0; i < h->length; 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) +/* fetch xattrs from an already-loaded inode */ +errcode_t ext2fs_xattrs_read_inode(struct ext2_xattr_handle *handle, + struct ext2_inode_large *inode) { - 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); @@ -784,6 +1044,10 @@ errcode_t ext2fs_xattrs_read(struct ext2_xattr_handle *handle) 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 + @@ -795,16 +1059,18 @@ errcode_t ext2fs_xattrs_read(struct ext2_xattr_handle *handle) 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); + 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); + blk = ext2fs_file_acl_block(handle->fs, EXT2_INODE(inode)); if (blk != 0) { if ((blk < handle->fs->super->s_first_data_block) || (blk >= ext2fs_blocks_count(handle->fs->super))) { @@ -832,46 +1098,66 @@ read_ea_block: 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; + err = read_xattrs_from_buffer(handle, inode, + (struct ext2_ext_attr_entry *) start, + storage_size, block_buf); + } +out3: + if (block_buf) ext2fs_free_mem(&block_buf); - } +out: + return err; +} - ext2fs_free_mem(&block_buf); - ext2fs_free_mem(&inode); - return 0; +errcode_t ext2fs_xattrs_read(struct ext2_xattr_handle *handle) +{ + struct ext2_inode_large *inode; + size_t inode_size = EXT2_INODE_SIZE(handle->fs->super); + errcode_t err; + + EXT2_CHECK_MAGIC(handle, EXT2_ET_MAGIC_EA_HANDLE); + + if (inode_size < sizeof(*inode)) + inode_size = sizeof(*inode); + err = ext2fs_get_memzero(inode_size, &inode); + if (err) + return err; + + err = ext2fs_read_inode_full(handle->fs, handle->ino, EXT2_INODE(inode), + EXT2_INODE_SIZE(handle->fs->super)); + if (err) + goto out; + + err = ext2fs_xattrs_read_inode(handle, inode); -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), + size_t value_len, + ext2_ino_t inode_num, 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->length; x++) { - if (!x->name) - continue; - - ret = func(x->name, x->value, x->value_len, data); + for (x = h->attrs; x < h->attrs + h->count; x++) { + ret = func(x->name, x->value, x->value_len, x->ea_ino, data); if (ret & XATTR_CHANGED) - h->dirty = 1; + dirty = 1; if (ret & XATTR_ABORT) - return 0; + break; } + if (dirty) + return ext2fs_xattrs_write(h); return 0; } @@ -883,11 +1169,17 @@ errcode_t ext2fs_xattr_get(struct ext2_xattr_handle *h, const char *key, 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) + for (x = h->attrs; x < h->attrs + h->count; x++) { + if (strcmp(x->name, key)) continue; - if (strcmp(x->name, key) == 0) { + 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; @@ -941,7 +1233,7 @@ errcode_t ext2fs_xattr_inode_max_size(ext2_filsys fs, ext2_ino_t ino, 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) { + if (!entry->e_value_inum && entry->e_value_size) { unsigned int offs = entry->e_value_offs; if (offs < minoff) minoff = offs; @@ -961,92 +1253,452 @@ out: return err; } -errcode_t ext2fs_xattr_set(struct ext2_xattr_handle *handle, - const char *key, - const void *value, - size_t value_len) +static errcode_t xattr_create_ea_inode(ext2_filsys fs, const void *value, + size_t value_len, ext2_ino_t *ea_ino) { - struct ext2_xattr *x, *last_empty; - char *new_value; - errcode_t err; + 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); - 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; - } + ret = ext2fs_write_inode(fs, ino, &inode); + if (ret) + return ret; - /* 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; - } - } + 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; - /* 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); + ext2fs_inode_alloc_stats2(fs, ino, 1 /* inuse */, 0 /* isdir */); - 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; - } + *ea_ino = ino; + return 0; +} - /* Expand array, append slot */ - err = ext2fs_xattrs_expand(handle, 4); - if (err) - return err; +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; - x = handle->attrs + handle->length - 4; - err = ext2fs_get_mem(strlen(key) + 1, &x->name); - if (err) - return err; - strcpy(x->name, key); + ret = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode, + sizeof(inode)); + if (ret) + goto out; - err = ext2fs_get_mem(value_len, &x->value); - if (err) - return err; - memcpy(x->value, value, value_len); + 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 char *short_name, + int index, 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; + x->short_name = new_name + (short_name - name); + } + x->name_index = index; + + if (x->value) + ext2fs_free_mem(&x->value); + x->value = new_value; x->value_len = value_len; - handle->dirty = 1; - handle->count++; + 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 *shortname, int name_idx) +{ + struct ext2_xattr *x; + int i; + int shortname_len, x_shortname_len; + + shortname_len = strlen(shortname); + + for (i = 0, x = attrs; i < count; i++, x++) { + if (name_idx < x->name_index) + break; + if (name_idx > x->name_index) + continue; + + x_shortname_len = strlen(x->short_name); + if (shortname_len < x_shortname_len) + break; + if (shortname_len > x_shortname_len) + continue; + + if (memcmp(shortname, x->short_name, 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 = 0; + const char *shortname = name; + 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, + shortname, name_idx, 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, + shortname, name_idx, 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, + shortname, name_idx); + 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, + shortname, name_idx); + 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, shortname, + name_idx, 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; + int i, len; + + for (i = 0, x = attrs; i < count; i++, x++) { + len = strlen(x->short_name); + 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 if (value_len) + 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 && + (!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, new_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, new_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, new_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 < handle->attrs + handle->length; x++) { - if (!x->name) - continue; - + for (x = handle->attrs; x < end; x++) { if (strcmp(x->name, key) == 0) { ext2fs_free_mem(&x->name); ext2fs_free_mem(&x->value); - x->value_len = 0; - handle->dirty = 1; + 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 0; + return ext2fs_xattrs_write(handle); } } @@ -1069,8 +1721,8 @@ errcode_t ext2fs_xattrs_open(ext2_filsys fs, ext2_ino_t ino, return err; h->magic = EXT2_ET_MAGIC_EA_HANDLE; - h->length = 4; - err = ext2fs_get_arrayzero(h->length, sizeof(struct ext2_xattr), + h->capacity = 4; + err = ext2fs_get_arrayzero(h->capacity, sizeof(struct ext2_xattr), &h->attrs); if (err) { ext2fs_free_mem(&h); @@ -1086,15 +1738,8 @@ errcode_t ext2fs_xattrs_open(ext2_filsys fs, ext2_ino_t ino, 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); @@ -1107,3 +1752,916 @@ errcode_t ext2fs_xattrs_count(struct ext2_xattr_handle *handle, size_t *count) *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; +} + +struct ext2_attr_info { + int name_index; + const char *name; + const char *value; + int value_len; +}; + +struct ext2_attr_search { + struct ext2_ext_attr_entry *first; + char *base; + char *end; + struct ext2_ext_attr_entry *here; + int not_found; +}; + +struct ext2_attr_ibody_find { + ext2_ino_t ino; + struct ext2_attr_search s; +}; + +struct ext2_attr_block_find { + struct ext2_attr_search s; + char *block; +}; + +void ext2fs_attr_shift_entries(struct ext2_ext_attr_entry *entry, + int value_offs_shift, char *to, + char *from, int n) +{ + struct ext2_ext_attr_entry *last = entry; + + /* Adjust the value offsets of the entries */ + for (; !EXT2_EXT_IS_LAST_ENTRY(last); last = EXT2_EXT_ATTR_NEXT(last)) { + if (!last->e_value_inum && last->e_value_size) { + last->e_value_offs = last->e_value_offs + + value_offs_shift; + } + } + /* Shift the entries by n bytes and zero freed space in inode */ + memmove(to, from, n); + if (to > from) + memset(from, 0, to - from); +} + +/* + * This function returns the free space present in the inode or the EA block. + * total is number of bytes taken up by the EA entries and is used to shift + * the EAs in ext2fs_expand_extra_isize(). + */ +int ext2fs_attr_free_space(struct ext2_ext_attr_entry *last, + int *min_offs, char *base, int *total) +{ + for (; !EXT2_EXT_IS_LAST_ENTRY(last); last = EXT2_EXT_ATTR_NEXT(last)) { + *total += EXT2_EXT_ATTR_LEN(last->e_name_len); + if (!last->e_value_inum && last->e_value_size) { + int offs = last->e_value_offs; + if (offs < *min_offs) + *min_offs = offs; + } + } + + return *min_offs - ((char *)last - base) - sizeof(__u32); +} + +static errcode_t ext2fs_attr_check_names(struct ext2_ext_attr_entry *entry, + char *end) +{ + while (!EXT2_EXT_IS_LAST_ENTRY(entry)) { + struct ext2_ext_attr_entry *next = EXT2_EXT_ATTR_NEXT(entry); + if ((char *)next >= end) + return EXT2_ET_EA_BAD_ENTRIES; + entry = next; + } + return 0; +} + +/* The unused parameter used to be the blocksize, but with in-inode xattrs + * the xattr storage area size depends on where the xattrs are kept. Keep + * this parameter for API/ABI compatibility, but it is not needed. */ +static errcode_t ext2fs_attr_find_entry(struct ext2_ext_attr_entry **pentry, + int name_index, const char *name, + int unused, int sorted) +{ + struct ext2_ext_attr_entry *entry; + int name_len; + int cmp = 1; + + if (name == NULL) + return EXT2_ET_EA_BAD_NAME; + + name_len = strlen(name); + entry = *pentry; + for (; !EXT2_EXT_IS_LAST_ENTRY(entry); + entry = EXT2_EXT_ATTR_NEXT(entry)) { + cmp = name_index - entry->e_name_index; + if (!cmp) + cmp = name_len - entry->e_name_len; + if (!cmp) + cmp = memcmp(name, entry->e_name, name_len); + if (cmp <= 0 && (sorted || cmp == 0)) + break; + } + *pentry = entry; + + return cmp ? EXT2_ET_EA_NAME_NOT_FOUND : 0; +} + +static errcode_t ext2fs_attr_block_find(ext2_filsys fs,struct ext2_inode *inode, + struct ext2_attr_info *i, + struct ext2_attr_block_find *bs) +{ + struct ext2_ext_attr_header *header; + errcode_t error; + + if (inode->i_file_acl) { + /* The inode already has an extended attribute block. */ + error = ext2fs_get_mem(fs->blocksize, &bs->block); + if (error) + return error; + error = ext2fs_read_ext_attr(fs, inode->i_file_acl, bs->block); + if (error) + goto cleanup; + + header = BHDR(bs->block); + if (header->h_magic != EXT2_EXT_ATTR_MAGIC) { + error = EXT2_ET_EA_BAD_MAGIC; + goto cleanup; + } + + /* Find the named attribute. */ + bs->s.base = bs->block; + bs->s.first = (struct ext2_ext_attr_entry *)(header + 1); + bs->s.end = bs->block + fs->blocksize; + bs->s.here = bs->s.first; + error = ext2fs_attr_find_entry(&bs->s.here, i->name_index, + i->name, fs->blocksize, 1); + if (error && error != EXT2_ET_EA_NAME_NOT_FOUND) + goto cleanup; + bs->s.not_found = error; + } + error = 0; + +cleanup: + if (error && bs->block) + ext2fs_free_mem(&bs->block); + return error; +} + +static errcode_t ext2fs_attr_ibody_find(ext2_filsys fs, + struct ext2_inode_large *inode, + struct ext2_attr_info *i, + struct ext2_attr_ibody_find *is) +{ + errcode_t error; + + if (EXT2_INODE_SIZE(fs->super) == EXT2_GOOD_OLD_INODE_SIZE) + return 0; + + if (inode->i_extra_isize == 0) + return 0; + + is->s.first = &IHDR(inode)->h_first_entry[0]; + is->s.base = (char *)is->s.first; + is->s.here = is->s.first; + is->s.end = (char *)inode + EXT2_INODE_SIZE(fs->super); + if (IHDR(inode)->h_magic == EXT2_EXT_ATTR_MAGIC) { + error = ext2fs_attr_check_names(is->s.first, is->s.end); + if (error) + return error; + /* Find the named attribute. */ + error = ext2fs_attr_find_entry(&is->s.here, i->name_index, + i->name, is->s.end - + (char *)is->s.base, 0); + if (error && error != EXT2_ET_EA_NAME_NOT_FOUND) + return error; + is->s.not_found = error; + } + + return 0; +} + +static errcode_t ext2fs_attr_set_entry(ext2_filsys fs, struct ext2_attr_info *i, + struct ext2_attr_search *s) +{ + struct ext2_ext_attr_entry *last; + int free, min_offs = s->end - s->base, name_len = strlen(i->name); + + /* Compute min_offs and last. */ + for (last = s->first; !EXT2_EXT_IS_LAST_ENTRY(last); + last = EXT2_EXT_ATTR_NEXT(last)) { + if (!last->e_value_inum && last->e_value_size) { + int offs = last->e_value_offs; + + if (offs < min_offs) + min_offs = offs; + } + } + free = min_offs - ((char *)last - s->base) - sizeof(__u32); + + if (!s->not_found) { + if (!s->here->e_value_inum && s->here->e_value_size) { + int size = s->here->e_value_size; + free += EXT2_EXT_ATTR_SIZE(size); + } + free += EXT2_EXT_ATTR_LEN(name_len); + } + if (i->value) { + if (free < EXT2_EXT_ATTR_LEN(name_len) + + EXT2_EXT_ATTR_SIZE(i->value_len)) + return EXT2_ET_EA_NO_SPACE; + } + + if (i->value && s->not_found) { + /* Insert the new name. */ + int size = EXT2_EXT_ATTR_LEN(name_len); + int rest = (char *)last - (char *)s->here + sizeof(__u32); + + memmove((char *)s->here + size, s->here, rest); + memset(s->here, 0, size); + s->here->e_name_index = i->name_index; + s->here->e_name_len = name_len; + memcpy(s->here->e_name, i->name, name_len); + } else { + if (!s->here->e_value_inum && s->here->e_value_size) { + char *first_val = s->base + min_offs; + int offs = s->here->e_value_offs; + char *val = s->base + offs; + int size = EXT2_EXT_ATTR_SIZE(s->here->e_value_size); + + if (i->value && + size == EXT2_EXT_ATTR_SIZE(i->value_len)) { + /* The old and the new value have the same + size. Just replace. */ + s->here->e_value_size = i->value_len; + memset(val + size - EXT2_EXT_ATTR_PAD, 0, + EXT2_EXT_ATTR_PAD); /* Clear pad bytes */ + memcpy(val, i->value, i->value_len); + return 0; + } + + /* Remove the old value. */ + memmove(first_val + size, first_val, val - first_val); + memset(first_val, 0, size); + s->here->e_value_size = 0; + s->here->e_value_offs = 0; + min_offs += size; + + /* Adjust all value offsets. */ + last = s->first; + while (!EXT2_EXT_IS_LAST_ENTRY(last)) { + int o = last->e_value_offs; + + if (!last->e_value_inum && + last->e_value_size && o < offs) + last->e_value_offs = o + size; + last = EXT2_EXT_ATTR_NEXT(last); + } + } + if (!i->value) { + /* Remove the old name. */ + int size = EXT2_EXT_ATTR_LEN(name_len); + + last = ENTRY((char *)last - size); + memmove((char *)s->here, (char *)s->here + size, + (char *)last - (char *)s->here + sizeof(__u32)); + memset(last, 0, size); + } + } + + if (i->value) { + /* Insert the new value. */ + s->here->e_value_size = i->value_len; + if (i->value_len) { + int size = EXT2_EXT_ATTR_SIZE(i->value_len); + char *val = s->base + min_offs - size; + + s->here->e_value_offs = min_offs - size; + memset(val + size - EXT2_EXT_ATTR_PAD, 0, + EXT2_EXT_ATTR_PAD); /* Clear the pad bytes. */ + memcpy(val, i->value, i->value_len); + } + } + + return 0; +} + +static errcode_t ext2fs_attr_block_set(ext2_filsys fs, struct ext2_inode *inode, + struct ext2_attr_info *i, + struct ext2_attr_block_find *bs) +{ + struct ext2_attr_search *s = &bs->s; + char *new_buf = NULL, *old_block = NULL; + blk_t blk; + int clear_flag = 0; + errcode_t error; + + if (i->value && i->value_len > fs->blocksize) + return EXT2_ET_EA_NO_SPACE; + + if (s->base) { + if (BHDR(s->base)->h_refcount != 1) { + int offset = (char *)s->here - bs->block; + + /* Decrement the refcount of the shared block */ + old_block = s->base; + BHDR(s->base)->h_refcount -= 1; + + error = ext2fs_get_mem(fs->blocksize, &s->base); + if (error) + goto cleanup; + clear_flag = 1; + memcpy(s->base, bs->block, fs->blocksize); + s->first = ENTRY(BHDR(s->base)+1); + BHDR(s->base)->h_refcount = 1; + s->here = ENTRY(s->base + offset); + s->end = s->base + fs->blocksize; + } + } else { + error = ext2fs_get_mem(fs->blocksize, &s->base); + if (error) + goto cleanup; + clear_flag = 1; + memset(s->base, 0, fs->blocksize); + BHDR(s->base)->h_magic = EXT2_EXT_ATTR_MAGIC; + BHDR(s->base)->h_blocks = 1; + BHDR(s->base)->h_refcount = 1; + s->first = ENTRY(BHDR(s->base)+1); + s->here = ENTRY(BHDR(s->base)+1); + s->end = s->base + fs->blocksize; + } + + error = ext2fs_attr_set_entry(fs, i, s); + if (error) + goto cleanup; + + if (!EXT2_EXT_IS_LAST_ENTRY(s->first)) + ext2fs_attr_rehash(BHDR(s->base), s->here); + + if (!EXT2_EXT_IS_LAST_ENTRY(s->first)) { + if (bs->block && bs->block == s->base) { + /* We are modifying this block in-place */ + new_buf = bs->block; + blk = inode->i_file_acl; + error = ext2fs_write_ext_attr(fs, blk, s->base); + if (error) + goto cleanup; + } else { + /* We need to allocate a new block */ + error = ext2fs_new_block(fs, 0, 0, &blk); + if (error) + goto cleanup; + ext2fs_block_alloc_stats(fs, blk, 1); + error = ext2fs_write_ext_attr(fs, blk, s->base); + if (error) + goto cleanup; + new_buf = s->base; + if (old_block) { + BHDR(s->base)->h_refcount -= 1; + error = ext2fs_write_ext_attr(fs, + inode->i_file_acl, + s->base); + if (error) + goto cleanup; + } + } + } + + /* Update the i_blocks if we added a new EA block */ + if (!inode->i_file_acl && new_buf) + inode->i_blocks += fs->blocksize / 512; + /* Update the inode. */ + inode->i_file_acl = new_buf ? blk : 0; + +cleanup: + if (clear_flag) + ext2fs_free_mem(&s->base); + return 0; +} + +static errcode_t ext2fs_attr_ibody_set(ext2_filsys fs, + struct ext2_inode_large *inode, + struct ext2_attr_info *i, + struct ext2_attr_ibody_find *is) +{ + struct ext2_attr_search *s = &is->s; + errcode_t error; + + if (EXT2_INODE_SIZE(fs->super) == EXT2_GOOD_OLD_INODE_SIZE) + return EXT2_ET_EA_NO_SPACE; + + error = ext2fs_attr_set_entry(fs, i, s); + if (error) + return error; + + if (!EXT2_EXT_IS_LAST_ENTRY(s->first)) + IHDR(inode)->h_magic = EXT2_EXT_ATTR_MAGIC; + else + IHDR(inode)->h_magic = 0; + + return ext2fs_write_inode_full(fs, is->ino, (struct ext2_inode *)inode, + EXT2_INODE_SIZE(fs->super)); +} + +static struct { + char str[28]; + int len; +} ext2_attr_index_prefix[] = { + [EXT2_ATTR_INDEX_USER] = { EXT2_ATTR_INDEX_USER_PREFIX, + sizeof(EXT2_ATTR_INDEX_USER_PREFIX) }, + [EXT2_ATTR_INDEX_POSIX_ACL_ACCESS] = { + EXT2_ATTR_INDEX_POSIX_ACL_ACCESS_PREFIX, + sizeof(EXT2_ATTR_INDEX_POSIX_ACL_ACCESS_PREFIX) }, + [EXT2_ATTR_INDEX_POSIX_ACL_DEFAULT] = { + EXT2_ATTR_INDEX_POSIX_ACL_DEFAULT_PREFIX, + sizeof(EXT2_ATTR_INDEX_POSIX_ACL_DEFAULT_PREFIX) }, + [EXT2_ATTR_INDEX_TRUSTED] = { EXT2_ATTR_INDEX_TRUSTED_PREFIX, + sizeof(EXT2_ATTR_INDEX_TRUSTED_PREFIX) }, + [EXT2_ATTR_INDEX_LUSTRE] = { EXT2_ATTR_INDEX_LUSTRE_PREFIX, + sizeof(EXT2_ATTR_INDEX_LUSTRE_PREFIX) }, + [EXT2_ATTR_INDEX_SECURITY] = { EXT2_ATTR_INDEX_SECURITY_PREFIX, + sizeof(EXT2_ATTR_INDEX_SECURITY_PREFIX)}, + { "", 0 } +}; + +errcode_t ext2fs_attr_set(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + int name_index, const char *name, const char *value, + int value_len, int flags) +{ + struct ext2_inode_large *inode_large = NULL; + struct ext2_attr_info i = { + .name_index = name_index, + .name = name, + .value = value, + .value_len = value_len, + }; + struct ext2_attr_ibody_find is = { + .ino = ino, + .s = { .not_found = -ENODATA, }, + }; + struct ext2_attr_block_find bs = { + .s = { .not_found = -ENODATA, }, + }; + errcode_t error; + + if (!name) + return EXT2_ET_EA_BAD_NAME; + if (strlen(name) > 255) + return EXT2_ET_EA_NAME_TOO_BIG; + + /* If the prefix is still present, skip it */ + if (strncmp(name, ext2_attr_index_prefix[name_index].str, + ext2_attr_index_prefix[name_index].len) == 0) + i.name += ext2_attr_index_prefix[name_index].len; + + if (EXT2_INODE_SIZE(fs->super) > EXT2_GOOD_OLD_INODE_SIZE) { + inode_large = (struct ext2_inode_large *)inode; + + error = ext2fs_attr_ibody_find(fs, inode_large, &i, &is); + if (error) + goto cleanup; + } + if (is.s.not_found) { + error = ext2fs_attr_block_find(fs, inode, &i, &bs); + if (error) + goto cleanup; + } + + if (is.s.not_found && bs.s.not_found) { + error = EXT2_ET_EA_NAME_NOT_FOUND; + if (flags & XATTR_REPLACE) + goto cleanup; + error = 0; + if (!value) + goto cleanup; + } else { + error = EXT2_ET_EA_NAME_EXISTS; + if (flags & XATTR_CREATE) + goto cleanup; + } + + if (!value) { + if (!is.s.not_found && + (EXT2_INODE_SIZE(fs->super) > EXT2_GOOD_OLD_INODE_SIZE)) + error = ext2fs_attr_ibody_set(fs, inode_large, &i, &is); + else if (!bs.s.not_found) + error = ext2fs_attr_block_set(fs, inode, &i, &bs); + } else { + if (EXT2_INODE_SIZE(fs->super) > EXT2_GOOD_OLD_INODE_SIZE) + error = ext2fs_attr_ibody_set(fs, inode_large, &i, &is); + if (!error && !bs.s.not_found) { + i.value = NULL; + error = ext2fs_attr_block_set(fs, inode, &i, &bs); + } else if (error == EXT2_ET_EA_NO_SPACE) { + error = ext2fs_attr_block_set(fs, inode, &i, &bs); + if (error) + goto cleanup; + if (!is.s.not_found) { + i.value = NULL; + if (EXT2_INODE_SIZE(fs->super) > + EXT2_GOOD_OLD_INODE_SIZE) + error = ext2fs_attr_ibody_set(fs, + inode_large, &i, &is); + } + } + } + +cleanup: + return error; +} + +static errcode_t ext2fs_attr_check_block(ext2_filsys fs, char *buffer) +{ + if (BHDR(buffer)->h_magic != (EXT2_EXT_ATTR_MAGIC) || + BHDR(buffer)->h_blocks != 1) + return EXT2_ET_EA_BAD_MAGIC; + + return ext2fs_attr_check_names((struct ext2_ext_attr_entry *) + (BHDR(buffer) + 1), + buffer + fs->blocksize); +} + +static errcode_t ext2fs_attr_block_get(ext2_filsys fs, struct ext2_inode *inode, + int name_index, const char *name, + void *buffer, size_t buffer_size, + int *easize) +{ + struct ext2_ext_attr_header *header = NULL; + struct ext2_ext_attr_entry *entry; + char *block_buf = NULL; + errcode_t error; + + error = EXT2_ET_EA_NAME_NOT_FOUND; + if (!inode->i_file_acl) + goto cleanup; + + error = ext2fs_get_mem(fs->blocksize, &block_buf); + if (error) + return error; + error = ext2fs_read_ext_attr(fs, inode->i_file_acl, block_buf); + if (error) + goto cleanup; + + error = ext2fs_attr_check_block(fs, block_buf); + if (error) + goto cleanup; + + header = BHDR(block_buf); + entry = (struct ext2_ext_attr_entry *)(header + 1); + error = ext2fs_attr_find_entry(&entry, name_index, name, + fs->blocksize, 1); + if (error) + goto cleanup; + if (easize) + *easize = entry->e_value_size; + if (buffer) { + if (entry->e_value_size > buffer_size) { + error = EXT2_ET_EA_TOO_BIG; + goto cleanup; + } + memcpy(buffer, block_buf + entry->e_value_offs, + entry->e_value_size); + error = 0; + } + +cleanup: + if (block_buf) + ext2fs_free_mem(&block_buf); + return error; +} + +static errcode_t ext2fs_attr_check_ibody(ext2_filsys fs, + struct ext2_inode_large *inode) +{ + const int inode_size = EXT2_INODE_SIZE(fs->super); + + if (inode_size == EXT2_GOOD_OLD_INODE_SIZE) + return EXT2_ET_EA_NAME_NOT_FOUND; + + if (IHDR(inode)->h_magic != EXT2_EXT_ATTR_MAGIC) + return EXT2_ET_EA_BAD_MAGIC; + + return ext2fs_attr_check_names(&IHDR(inode)->h_first_entry[0], + (char *)inode + inode_size); +} + + +static errcode_t ext2fs_attr_ibody_get(ext2_filsys fs, + struct ext2_inode_large *inode, + int name_index, const char *name, + void *buffer, size_t buffer_size, + int *easize) +{ + struct ext2_ext_attr_entry *entry; + int error; + + error = ext2fs_attr_check_ibody(fs, inode); + if (error) + return error; + + entry = &IHDR(inode)->h_first_entry[0]; + + error = ext2fs_attr_find_entry(&entry, name_index, name, + (char *)inode + EXT2_INODE_SIZE(fs->super) - + (char *)entry, 0); + if (error) + goto cleanup; + if (easize) + *easize = entry->e_value_size; + if (buffer) { + if (entry->e_value_size > buffer_size) { + error = EXT2_ET_EA_TOO_BIG; + goto cleanup; + } + memcpy(buffer, (char *)&IHDR(inode)->h_first_entry[0] + + entry->e_value_offs, entry->e_value_size); + } + +cleanup: + return error; +} + + +errcode_t ext2fs_attr_get(ext2_filsys fs, struct ext2_inode *inode, + int name_index, const char *name, char *buffer, + size_t buffer_size, int *easize) +{ + errcode_t error; + + error = ext2fs_attr_ibody_get(fs, (struct ext2_inode_large *)inode, + name_index, name, buffer, buffer_size, + easize); + if (error == EXT2_ET_EA_NAME_NOT_FOUND || error == EXT2_ET_EA_BAD_MAGIC) + error = ext2fs_attr_block_get(fs, inode, name_index, name, + buffer, buffer_size, easize); + + return error; +} + +int ext2fs_attr_get_next_attr(struct ext2_ext_attr_entry *entry, int name_index, + char *buffer, int buffer_size, int start) +{ + const int prefix_len = ext2_attr_index_prefix[name_index].len; + int total_len; + + if (!start && !EXT2_EXT_IS_LAST_ENTRY(entry)) + entry = EXT2_EXT_ATTR_NEXT(entry); + + for (; !EXT2_EXT_IS_LAST_ENTRY(entry); + entry = EXT2_EXT_ATTR_NEXT(entry)) { + if (!name_index) + break; + if (name_index == entry->e_name_index) + break; + } + if (EXT2_EXT_IS_LAST_ENTRY(entry)) + return 0; + + total_len = prefix_len + entry->e_name_len + 1; + if (buffer && total_len <= buffer_size) { + memcpy(buffer, ext2_attr_index_prefix[name_index].str, + prefix_len); + memcpy(buffer + prefix_len, entry->e_name, entry->e_name_len); + buffer[prefix_len + entry->e_name_len] = '\0'; + } + + return total_len; +} + +errcode_t ext2fs_expand_extra_isize(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode_large *inode, + int new_extra_isize, int *ret, + int *needed_size) +{ + struct ext2_inode *inode_buf = NULL; + struct ext2_ext_attr_header *header = NULL; + struct ext2_ext_attr_entry *entry = NULL, *last = NULL; + struct ext2_attr_ibody_find is = { + .ino = ino, + .s = { .not_found = EXT2_ET_EA_NO_SPACE, }, + }; + struct ext2_attr_block_find bs = { + .s = { .not_found = EXT2_ET_EA_NO_SPACE, }, + }; + char *start, *end, *block_buf = NULL, *buffer =NULL, *b_entry_name=NULL; + int total_ino = 0, total_blk, free, offs, tried_min_extra_isize = 0; + int s_min_extra_isize = fs->super->s_min_extra_isize; + errcode_t error = 0; + + if (needed_size) + *needed_size = new_extra_isize; + error = ext2fs_get_mem(fs->blocksize, &block_buf); + if (error) + return error; + + if (inode == NULL) { + error = ext2fs_get_mem(EXT2_INODE_SIZE(fs->super), &inode_buf); + if (error) + goto cleanup; + + error = ext2fs_read_inode_full(fs, ino, inode_buf, + EXT2_INODE_SIZE(fs->super)); + if (error) + goto cleanup; + + inode = (struct ext2_inode_large *)inode_buf; + } + +retry: + if (inode->i_extra_isize >= new_extra_isize) + goto cleanup; + + start = (char *)inode + EXT2_GOOD_OLD_INODE_SIZE + inode->i_extra_isize; + /* No extended attributes present */ + if (IHDR(inode)->h_magic != EXT2_EXT_ATTR_MAGIC) { + memset(start, 0, + EXT2_INODE_SIZE(fs->super) - EXT2_GOOD_OLD_INODE_SIZE - + inode->i_extra_isize); + inode->i_extra_isize = new_extra_isize; + if (needed_size) + *needed_size = 0; + goto write_inode; + } + + start += sizeof(__u32); + end = (char *)inode + EXT2_INODE_SIZE(fs->super); + last = entry = (struct ext2_ext_attr_entry *)start; + offs = end - start; + /* Consider space takenup by magic number */ + total_ino = sizeof(__u32); + free = ext2fs_attr_free_space(last, &offs, start, &total_ino); + + /* Enough free space available in the inode for expansion */ + if (free >= new_extra_isize) { + ext2fs_attr_shift_entries(entry, + inode->i_extra_isize - new_extra_isize, + (char *)inode + + EXT2_GOOD_OLD_INODE_SIZE + + new_extra_isize, + start - sizeof(__u32), total_ino); + inode->i_extra_isize = new_extra_isize; + if (needed_size) + *needed_size = 0; + goto write_inode; + } + + if (inode->i_file_acl) { + error = ext2fs_read_ext_attr(fs, inode->i_file_acl, block_buf); + if (error) + goto cleanup; + + header = BHDR(block_buf); + if (header->h_magic != EXT2_EXT_ATTR_MAGIC) { + error = EXT2_ET_EA_BAD_MAGIC; + goto cleanup; + } + end = block_buf + fs->blocksize; + last = entry = (struct ext2_ext_attr_entry *)(header+1); + start = (char *)entry; + offs = end - start; + free = ext2fs_attr_free_space(last, &offs, start, &total_blk); + if (free < new_extra_isize) { + if (!tried_min_extra_isize && s_min_extra_isize) { + tried_min_extra_isize++; + new_extra_isize = s_min_extra_isize; + goto retry; + } + if (ret) + *ret = EXT2_EXPAND_EISIZE_NOSPC; + error = EXT2_ET_EA_NO_SPACE; + goto cleanup; + } + } else { + if (ret && *ret == EXT2_EXPAND_EISIZE_UNSAFE) { + *ret = EXT2_EXPAND_EISIZE_NEW_BLOCK; + error = 0; + goto cleanup; + } + free = fs->blocksize; + } + + while (new_extra_isize > 0) { + int offs, size, entry_size; + struct ext2_ext_attr_entry *small_entry = NULL; + struct ext2_attr_info i = { + .value = NULL, + .value_len = 0, + }; + unsigned int total_size, shift_bytes, temp = ~0U, extra_isize=0; + + start = (char *)inode + EXT2_GOOD_OLD_INODE_SIZE + + inode->i_extra_isize + sizeof(__u32); + end = (char *)inode + EXT2_INODE_SIZE(fs->super); + last = (struct ext2_ext_attr_entry *)start; + + /* Find the entry best suited to be pushed into EA block */ + entry = NULL; + for (; !EXT2_EXT_IS_LAST_ENTRY(last); + last = EXT2_EXT_ATTR_NEXT(last)) { + total_size = EXT2_EXT_ATTR_SIZE(last->e_value_size) + + EXT2_EXT_ATTR_LEN(last->e_name_len); + if (total_size <= free && total_size < temp) { + if (total_size < new_extra_isize) { + small_entry = last; + } else { + entry = last; + temp = total_size; + } + } + } + + if (entry == NULL) { + if (small_entry) { + entry = small_entry; + } else { + if (!tried_min_extra_isize && + s_min_extra_isize) { + tried_min_extra_isize++; + new_extra_isize = s_min_extra_isize; + goto retry; + } + if (ret) + *ret = EXT2_EXPAND_EISIZE_NOSPC; + error = EXT2_ET_EA_NO_SPACE; + goto cleanup; + } + } + offs = entry->e_value_offs; + size = entry->e_value_size; + entry_size = EXT2_EXT_ATTR_LEN(entry->e_name_len); + i.name_index = entry->e_name_index; + error = ext2fs_get_mem(EXT2_EXT_ATTR_SIZE(size), &buffer); + if (error) + goto cleanup; + error = ext2fs_get_mem(entry->e_name_len + 1, &b_entry_name); + if (error) + goto cleanup; + /* Save the entry name and the entry value */ + memcpy((char *)buffer, (char *)start + offs, + EXT2_EXT_ATTR_SIZE(size)); + memcpy((char *)b_entry_name, (char *)entry->e_name, + entry->e_name_len); + b_entry_name[entry->e_name_len] = '\0'; + i.name = b_entry_name; + + error = ext2fs_attr_ibody_find(fs, inode, &i, &is); + if (error) + goto cleanup; + + error = ext2fs_attr_set_entry(fs, &i, &is.s); + if (error) + goto cleanup; + + entry = (struct ext2_ext_attr_entry *)start; + if (entry_size + EXT2_EXT_ATTR_SIZE(size) >= new_extra_isize) + shift_bytes = new_extra_isize; + else + shift_bytes = entry_size + EXT2_EXT_ATTR_SIZE(size); + ext2fs_attr_shift_entries(entry, + inode->i_extra_isize - shift_bytes, + (char *)inode +EXT2_GOOD_OLD_INODE_SIZE+ + extra_isize + shift_bytes, + start - sizeof(__u32), + total_ino - entry_size); + + extra_isize += shift_bytes; + new_extra_isize -= shift_bytes; + if (needed_size) + *needed_size = new_extra_isize; + inode->i_extra_isize = extra_isize; + + i.name = b_entry_name; + i.value = buffer; + i.value_len = size; + error = ext2fs_attr_block_find(fs, (struct ext2_inode *)inode, + &i, &bs); + if (error) + goto cleanup; + + /* Add entry which was removed from the inode into the block */ + error = ext2fs_attr_block_set(fs, (struct ext2_inode *)inode, + &i, &bs); + if (error) + goto cleanup; + } + +write_inode: + error = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)inode, + EXT2_INODE_SIZE(fs->super)); +cleanup: + if (inode_buf) + ext2fs_free_mem(&inode_buf); + if (block_buf) + ext2fs_free_mem(&block_buf); + if (buffer) + ext2fs_free_mem(&buffer); + if (b_entry_name) + ext2fs_free_mem(&b_entry_name); + + return error; +}