X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=lwext4%2Fext4_xattr.c;h=f498f8f688b89b4e78d4a25b80e60b9381cdc142;hb=2a7a75f8f98d051f8d2b7b78d31649e943329fa6;hp=ad1f9f3bbeb3dc46e5af00149203e1df1275e793;hpb=ce0087be0d06ad6efc332ba0c8146aa29d439b2d;p=lwext4.git diff --git a/lwext4/ext4_xattr.c b/lwext4/ext4_xattr.c index ad1f9f3..f498f8f 100644 --- a/lwext4/ext4_xattr.c +++ b/lwext4/ext4_xattr.c @@ -1,3 +1,39 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_xattr.c + * @brief Extended Attribute manipulation. + */ + #include "ext4_config.h" #include "ext4_types.h" #include "ext4_fs.h" @@ -13,99 +49,109 @@ #include #include -static int ext4_xattr_item_cmp(struct ext4_rb_node *a_, - struct ext4_rb_node *b_) -{ - struct ext4_xattr_item *a, *b; - int result; - a = container_of(a_, struct ext4_xattr_item, node); - b = container_of(b_, struct ext4_xattr_item, node); - result = a->name_index - b->name_index; - if (result) - return result; +/** + * @file ext4_xattr.c + * @brief Extended Attribute Manipulation + */ - result = a->name_len - b->name_len; - if (result) - return result; +#define NAME_HASH_SHIFT 5 +#define VALUE_HASH_SHIFT 16 - return memcmp(a->name, b->name, a->name_len); -} +static inline void ext4_xattr_compute_hash(struct ext4_xattr_header *header, + struct ext4_xattr_entry *entry) +{ + uint32_t hash = 0; + char *name = EXT4_XATTR_NAME(entry); + int n; -static struct ext4_xattr_item * -ext4_xattr_item_lookup(struct ext4_rb_root *root, - uint8_t name_index, - char *name, - size_t name_len) -{ - struct ext4_rb_node *new = root->ext4_rb_node; - - /* Figure out where to put new node */ - while (new) { - struct ext4_xattr_item *item = - container_of(new, struct ext4_xattr_item, node); - int result = name_index - item->name_index; - if (!result) { - result = name_len - item->name_len; - if (!result) - result = memcmp(name, - item->name, name_len); + for (n = 0; n < entry->e_name_len; n++) { + hash = (hash << NAME_HASH_SHIFT) ^ + (hash >> (8 * sizeof(hash) - NAME_HASH_SHIFT)) ^ *name++; + } + if (entry->e_value_block == 0 && entry->e_value_size != 0) { + uint32_t *value = + (uint32_t *)((char *)header + to_le16(entry->e_value_offs)); + for (n = (to_le32(entry->e_value_size) + EXT4_XATTR_ROUND) >> + EXT4_XATTR_PAD_BITS; + n; n--) { + hash = (hash << VALUE_HASH_SHIFT) ^ + (hash >> (8 * sizeof(hash) - VALUE_HASH_SHIFT)) ^ + to_le32(*value++); } - - if (result < 0) - new = new->ext4_rb_left; - else if (result > 0) - new = new->ext4_rb_right; - else - return item; - } - - return NULL; + entry->e_hash = to_le32(hash); } -static void -ext4_xattr_item_insert(struct ext4_rb_root *root, - struct ext4_xattr_item *item) +#define BLOCK_HASH_SHIFT 16 + +/* + * ext4_xattr_rehash() + * + * Re-compute the extended attribute hash value after an entry has changed. + */ +static void ext4_xattr_rehash(struct ext4_xattr_header *header, + struct ext4_xattr_entry *entry) { - ext4_rb_insert(root, &item->node, ext4_xattr_item_cmp); + struct ext4_xattr_entry *here; + uint32_t hash = 0; + + ext4_xattr_compute_hash(header, entry); + here = EXT4_XATTR_ENTRY(header + 1); + while (!EXT4_XATTR_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)) ^ + to_le32(here->e_hash); + here = EXT4_XATTR_NEXT(here); + } + header->h_hash = to_le32(hash); } -static void -ext4_xattr_item_remove(struct ext4_rb_root *root, - struct ext4_xattr_item *item) +static int ext4_xattr_item_cmp(struct ext4_xattr_item *a, + struct ext4_xattr_item *b) { - ext4_rb_erase(&item->node, root); + int result; + result = a->name_index - b->name_index; + if (result) + return result; + + result = a->name_len - b->name_len; + if (result) + return result; + + return memcmp(a->name, b->name, a->name_len); } +RB_GENERATE_INTERNAL(ext4_xattr_tree, ext4_xattr_item, node, + ext4_xattr_item_cmp, static inline) static struct ext4_xattr_item * -ext4_xattr_item_alloc(uint8_t name_index, - char *name, - size_t name_len) +ext4_xattr_item_alloc(uint8_t name_index, char *name, size_t name_len) { struct ext4_xattr_item *item; - item = malloc(sizeof(struct ext4_xattr_item) + - name_len); + item = malloc(sizeof(struct ext4_xattr_item) + name_len); if (!item) return NULL; item->name_index = name_index; - item->name = (char *)(item + 1); - item->name_len = name_len; - item->data = NULL; - item->data_size = 0; - + item->name = (char *)(item + 1); + item->name_len = name_len; + item->data = NULL; + item->data_size = 0; + memset(&item->node, 0, sizeof(item->node)); memcpy(item->name, name, name_len); return item; } -static int -ext4_xattr_item_alloc_data(struct ext4_xattr_item *item, - void *orig_data, - size_t data_size) +static int ext4_xattr_item_alloc_data(struct ext4_xattr_item *item, + void *orig_data, size_t data_size) { void *data = NULL; ext4_assert(!item->data); @@ -121,8 +167,7 @@ ext4_xattr_item_alloc_data(struct ext4_xattr_item *item, return EOK; } -static void -ext4_xattr_item_free_data(struct ext4_xattr_item *item) +static void ext4_xattr_item_free_data(struct ext4_xattr_item *item) { ext4_assert(item->data); free(item->data); @@ -130,9 +175,8 @@ ext4_xattr_item_free_data(struct ext4_xattr_item *item) item->data_size = 0; } -static int -ext4_xattr_item_resize_data(struct ext4_xattr_item *item, - size_t new_data_size) +static int ext4_xattr_item_resize_data(struct ext4_xattr_item *item, + size_t new_data_size) { if (new_data_size != item->data_size) { void *new_data; @@ -146,8 +190,7 @@ ext4_xattr_item_resize_data(struct ext4_xattr_item *item, return EOK; } -static void -ext4_xattr_item_free(struct ext4_xattr_item *item) +static void ext4_xattr_item_free(struct ext4_xattr_item *item) { if (item->data) ext4_xattr_item_free_data(item); @@ -155,7 +198,6 @@ ext4_xattr_item_free(struct ext4_xattr_item *item) free(item); } - static void *ext4_xattr_entry_data(struct ext4_xattr_ref *xattr_ref, struct ext4_xattr_entry *entry, bool in_inode) @@ -164,24 +206,26 @@ static void *ext4_xattr_entry_data(struct ext4_xattr_ref *xattr_ref, if (in_inode) { struct ext4_xattr_ibody_header *header; struct ext4_xattr_entry *first_entry; - uint16_t inode_size = ext4_get16(&xattr_ref->fs->sb, - inode_size); + int16_t inode_size = + ext4_get16(&xattr_ref->fs->sb, inode_size); header = EXT4_XATTR_IHDR(xattr_ref->inode_ref->inode); first_entry = EXT4_XATTR_IFIRST(header); - - ret = (void *)((char *)first_entry + to_le16(entry->e_value_offs)); - if ((char *)ret + EXT4_XATTR_SIZE(to_le32(entry->e_value_size)) - - (char *)xattr_ref->inode_ref->inode > + + ret = (void *)((char *)first_entry + + to_le16(entry->e_value_offs)); + if ((char *)ret + + EXT4_XATTR_SIZE(to_le32(entry->e_value_size)) - + (char *)xattr_ref->inode_ref->inode > inode_size) ret = NULL; } else { - uint32_t block_size = - ext4_sb_get_block_size(&xattr_ref->fs->sb); - ret = (void *)((char *)xattr_ref->block.data + - to_le16(entry->e_value_offs)); - if ((char *)ret + EXT4_XATTR_SIZE(to_le32(entry->e_value_size)) - - (char *)xattr_ref->block.data > + int32_t block_size = ext4_sb_get_block_size(&xattr_ref->fs->sb); + ret = (void *)((char *)xattr_ref->block.data + + to_le16(entry->e_value_offs)); + if ((char *)ret + + EXT4_XATTR_SIZE(to_le32(entry->e_value_size)) - + (char *)xattr_ref->block.data > block_size) ret = NULL; } @@ -199,37 +243,33 @@ static int ext4_xattr_block_fetch(struct ext4_xattr_ref *xattr_ref) entry = EXT4_XATTR_BFIRST(&xattr_ref->block); size_rem = ext4_sb_get_block_size(&xattr_ref->fs->sb); - for(;size_rem > 0 && !EXT4_XATTR_IS_LAST_ENTRY(entry); - entry = EXT4_XATTR_NEXT(entry), - size_rem -= EXT4_XATTR_LEN(entry->e_name_len)) { + for (; size_rem > 0 && !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry), + size_rem -= EXT4_XATTR_LEN(entry->e_name_len)) { struct ext4_xattr_item *item; - char *e_name = (char *)(entry + 1); + char *e_name = EXT4_XATTR_NAME(entry); - data = ext4_xattr_entry_data(xattr_ref, entry, - false); + data = ext4_xattr_entry_data(xattr_ref, entry, false); if (!data) { ret = EIO; goto Finish; } - item = ext4_xattr_item_alloc(entry->e_name_index, - e_name, + item = ext4_xattr_item_alloc(entry->e_name_index, e_name, (size_t)entry->e_name_len); if (!item) { ret = ENOMEM; goto Finish; } - if (ext4_xattr_item_alloc_data(item, - data, - to_le32(entry->e_value_size) - != EOK)) { + if (ext4_xattr_item_alloc_data( + item, data, to_le32(entry->e_value_size)) != EOK) { ext4_xattr_item_free(item); ret = ENOMEM; goto Finish; } - ext4_xattr_item_insert(&xattr_ref->root, - item); - + RB_INSERT(ext4_xattr_tree, &xattr_ref->root, item); + xattr_ref->ea_size += EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len); } Finish: @@ -243,63 +283,67 @@ static int ext4_xattr_inode_fetch(struct ext4_xattr_ref *xattr_ref) int ret = EOK; struct ext4_xattr_ibody_header *header = NULL; struct ext4_xattr_entry *entry = NULL; - uint16_t inode_size = ext4_get16(&xattr_ref->fs->sb, - inode_size); + uint16_t inode_size = ext4_get16(&xattr_ref->fs->sb, inode_size); header = EXT4_XATTR_IHDR(xattr_ref->inode_ref->inode); entry = EXT4_XATTR_IFIRST(header); - size_rem = inode_size - - EXT4_GOOD_OLD_INODE_SIZE - - xattr_ref->inode_ref->inode->extra_isize; - for(;size_rem > 0 && !EXT4_XATTR_IS_LAST_ENTRY(entry); - entry = EXT4_XATTR_NEXT(entry), - size_rem -= EXT4_XATTR_LEN(entry->e_name_len)) { + size_rem = inode_size - EXT4_GOOD_OLD_INODE_SIZE - + xattr_ref->inode_ref->inode->extra_isize; + for (; size_rem > 0 && !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry), + size_rem -= EXT4_XATTR_LEN(entry->e_name_len)) { struct ext4_xattr_item *item; - char *e_name = (char *)(entry + 1); + char *e_name = EXT4_XATTR_NAME(entry); - data = ext4_xattr_entry_data(xattr_ref, entry, - true); + data = ext4_xattr_entry_data(xattr_ref, entry, true); if (!data) { ret = EIO; goto Finish; } - item = ext4_xattr_item_alloc(entry->e_name_index, - e_name, + item = ext4_xattr_item_alloc(entry->e_name_index, e_name, (size_t)entry->e_name_len); if (!item) { ret = ENOMEM; goto Finish; } - if (ext4_xattr_item_alloc_data(item, - data, - to_le32(entry->e_value_size) - != EOK)) { + if (ext4_xattr_item_alloc_data( + item, data, to_le32(entry->e_value_size)) != EOK) { ext4_xattr_item_free(item); ret = ENOMEM; goto Finish; } - ext4_xattr_item_insert(&xattr_ref->root, - item); - - + RB_INSERT(ext4_xattr_tree, &xattr_ref->root, item); + xattr_ref->ea_size += EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len); } Finish: return ret; } +static size_t ext4_xattr_inode_space(struct ext4_xattr_ref *xattr_ref) +{ + uint16_t inode_size = ext4_get16(&xattr_ref->fs->sb, inode_size); + uint16_t size_rem = inode_size - EXT4_GOOD_OLD_INODE_SIZE - + xattr_ref->inode_ref->inode->extra_isize; + return size_rem; +} + +static size_t ext4_xattr_block_space(struct ext4_xattr_ref *xattr_ref) +{ + return ext4_sb_get_block_size(&xattr_ref->fs->sb); +} + static int ext4_xattr_fetch(struct ext4_xattr_ref *xattr_ref) { int ret = EOK; - uint16_t inode_size = ext4_get16(&xattr_ref->fs->sb, - inode_size); + uint16_t inode_size = ext4_get16(&xattr_ref->fs->sb, inode_size); if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) { ret = ext4_xattr_inode_fetch(xattr_ref); if (ret != EOK) return ret; - } if (xattr_ref->block_loaded) @@ -309,33 +353,420 @@ static int ext4_xattr_fetch(struct ext4_xattr_ref *xattr_ref) return ret; } -static void -ext4_xattr_purge_items(struct ext4_xattr_ref *xattr_ref) +static struct ext4_xattr_item * +ext4_xattr_lookup_item(struct ext4_xattr_ref *xattr_ref, uint8_t name_index, + char *name, size_t name_len) { - struct ext4_rb_node *node; - while ((node = ext4_rb_first(&xattr_ref->root))) { - struct ext4_xattr_item *item = ext4_rb_entry( - node, struct ext4_xattr_item, node); - ext4_xattr_item_remove(&xattr_ref->root, item); + struct ext4_xattr_item tmp, *ret; + tmp.name_index = name_index; + tmp.name = name; + tmp.name_len = name_len; + ret = RB_FIND(ext4_xattr_tree, &xattr_ref->root, &tmp); + return ret; +} + +static struct ext4_xattr_item * +ext4_xattr_insert_item(struct ext4_xattr_ref *xattr_ref, uint8_t name_index, + char *name, size_t name_len, void *data, + size_t data_size) +{ + struct ext4_xattr_item *item; + item = ext4_xattr_item_alloc(name_index, name, name_len); + if (!item) + return NULL; + + if (xattr_ref->ea_size + EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len) > + ext4_xattr_inode_space(xattr_ref) + + ext4_xattr_block_space(xattr_ref)) { ext4_xattr_item_free(item); + return NULL; } + if (ext4_xattr_item_alloc_data(item, data, data_size) != EOK) { + ext4_xattr_item_free(item); + return NULL; + } + RB_INSERT(ext4_xattr_tree, &xattr_ref->root, item); + xattr_ref->ea_size += + EXT4_XATTR_SIZE(item->data_size) + EXT4_XATTR_LEN(item->name_len); + xattr_ref->dirty = true; + return item; +} + +static int ext4_xattr_remove_item(struct ext4_xattr_ref *xattr_ref, + uint8_t name_index, char *name, + size_t name_len) +{ + int ret = ENOENT; + struct ext4_xattr_item *item = + ext4_xattr_lookup_item(xattr_ref, name_index, name, name_len); + if (item) { + if (item == xattr_ref->iter_from) + xattr_ref->iter_from = + RB_NEXT(ext4_xattr_tree, &xattr_ref->root, item); + + RB_REMOVE(ext4_xattr_tree, &xattr_ref->root, item); + ext4_xattr_item_free(item); + xattr_ref->ea_size -= EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len); + xattr_ref->dirty = true; + ret = EOK; + } + return ret; } -int ext4_fs_get_xattr_ref(struct ext4_fs *fs, - struct ext4_inode_ref *inode_ref, +static int ext4_xattr_resize_item(struct ext4_xattr_ref *xattr_ref, + struct ext4_xattr_item *item, + size_t new_data_size) +{ + int ret = EOK; + if (xattr_ref->ea_size - EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_SIZE(new_data_size) > + ext4_xattr_inode_space(xattr_ref) + + ext4_xattr_block_space(xattr_ref)) { + + return ENOSPC; + } + ret = ext4_xattr_item_resize_data(item, new_data_size); + if (ret != EOK) { + return ret; + } + xattr_ref->ea_size -= + EXT4_XATTR_SIZE(item->data_size) + EXT4_XATTR_SIZE(new_data_size); + xattr_ref->dirty = true; + return ret; +} + +static void ext4_xattr_purge_items(struct ext4_xattr_ref *xattr_ref) +{ + struct ext4_xattr_item *item, *save_item; + uint64_t xattr_block = ext4_inode_get_file_acl( + xattr_ref->inode_ref->inode, &xattr_ref->fs->sb); + RB_FOREACH_SAFE(item, ext4_xattr_tree, &xattr_ref->root, save_item) + { + RB_REMOVE(ext4_xattr_tree, &xattr_ref->root, item); + ext4_xattr_item_free(item); + } + xattr_ref->ea_size = 0; + if (xattr_block) + xattr_ref->ea_size += sizeof(struct ext4_xattr_header); + + if (ext4_xattr_inode_space(xattr_ref) > + sizeof(struct ext4_xattr_ibody_header)) + xattr_ref->ea_size += sizeof(struct ext4_xattr_ibody_header); +} + +static int ext4_xattr_try_alloc_block(struct ext4_xattr_ref *xattr_ref) +{ + int ret = EOK; + + uint64_t xattr_block = 0; + xattr_block = ext4_inode_get_file_acl(xattr_ref->inode_ref->inode, + &xattr_ref->fs->sb); + if (!xattr_block) { + ret = ext4_balloc_alloc_block(xattr_ref->inode_ref, + (uint32_t *)&xattr_block); + if (ret != EOK) + goto Finish; + + ret = ext4_block_get(xattr_ref->fs->bdev, &xattr_ref->block, + xattr_block); + if (ret != EOK) { + ext4_balloc_free_block(xattr_ref->inode_ref, + xattr_block); + goto Finish; + } + + ext4_inode_set_file_acl(xattr_ref->inode_ref->inode, + &xattr_ref->fs->sb, xattr_block); + xattr_ref->inode_ref->dirty = true; + xattr_ref->block_loaded = true; + xattr_ref->ea_size += sizeof(struct ext4_xattr_header); + } + +Finish: + return ret; +} + +static void ext4_xattr_try_free_block(struct ext4_xattr_ref *xattr_ref) +{ + uint64_t xattr_block; + xattr_block = ext4_inode_get_file_acl(xattr_ref->inode_ref->inode, + &xattr_ref->fs->sb); + ext4_inode_set_file_acl(xattr_ref->inode_ref->inode, &xattr_ref->fs->sb, + 0); + ext4_block_set(xattr_ref->fs->bdev, &xattr_ref->block); + ext4_balloc_free_block(xattr_ref->inode_ref, xattr_block); + xattr_ref->inode_ref->dirty = true; + xattr_ref->block_loaded = false; + xattr_ref->ea_size -= sizeof(struct ext4_xattr_header); +} + +static void ext4_xattr_set_block_header(struct ext4_xattr_ref *xattr_ref) +{ + struct ext4_xattr_header *block_header = NULL; + block_header = EXT4_XATTR_BHDR(&xattr_ref->block); + + memset(block_header, 0, sizeof(struct ext4_xattr_header)); + block_header->h_magic = EXT4_XATTR_MAGIC; + block_header->h_refcount = to_le32(1); + block_header->h_blocks = to_le32(1); +} + +static void +ext4_xattr_set_inode_entry(struct ext4_xattr_item *item, + struct ext4_xattr_ibody_header *ibody_header, + struct ext4_xattr_entry *entry, void *ibody_data_ptr) +{ + entry->e_name_len = to_le32(item->name_len); + entry->e_name_index = item->name_index; + entry->e_value_offs = + (char *)ibody_data_ptr - (char *)EXT4_XATTR_IFIRST(ibody_header); + entry->e_value_block = 0; + entry->e_value_size = item->data_size; +} + +static void ext4_xattr_set_block_entry(struct ext4_xattr_item *item, + struct ext4_xattr_header *block_header, + struct ext4_xattr_entry *block_entry, + void *block_data_ptr) +{ + block_entry->e_name_len = to_le32(item->name_len); + block_entry->e_name_index = item->name_index; + block_entry->e_value_offs = + (char *)block_data_ptr - (char *)block_header; + block_entry->e_value_block = 0; + block_entry->e_value_size = item->data_size; +} + +static int ext4_xattr_write_to_disk(struct ext4_xattr_ref *xattr_ref) +{ + int ret = EOK; + bool block_modified = false; + void *ibody_data = 0; + void *block_data = 0; + struct ext4_xattr_item *item, *save_item; + size_t inode_size_rem, block_size_rem; + struct ext4_xattr_ibody_header *ibody_header = NULL; + struct ext4_xattr_header *block_header = NULL; + struct ext4_xattr_entry *entry = NULL; + struct ext4_xattr_entry *block_entry = NULL; + + inode_size_rem = ext4_xattr_inode_space(xattr_ref); + block_size_rem = ext4_xattr_block_space(xattr_ref); + if (inode_size_rem > sizeof(struct ext4_xattr_ibody_header)) { + ibody_header = EXT4_XATTR_IHDR(xattr_ref->inode_ref->inode); + entry = EXT4_XATTR_IFIRST(ibody_header); + } + + if (!xattr_ref->dirty) + goto Finish; + /* If there are enough spaces in the ibody EA table.*/ + if (inode_size_rem > sizeof(struct ext4_xattr_ibody_header)) { + memset(ibody_header, 0, inode_size_rem); + ibody_header->h_magic = EXT4_XATTR_MAGIC; + ibody_data = (char *)ibody_header + inode_size_rem; + inode_size_rem -= sizeof(struct ext4_xattr_ibody_header); + + xattr_ref->inode_ref->dirty = true; + } + /* If we need an extra block to hold the EA entries*/ + if (xattr_ref->ea_size > inode_size_rem) { + if (!xattr_ref->block_loaded) { + ret = ext4_xattr_try_alloc_block(xattr_ref); + if (ret != EOK) + goto Finish; + } + block_header = EXT4_XATTR_BHDR(&xattr_ref->block); + block_entry = EXT4_XATTR_BFIRST(&xattr_ref->block); + ext4_xattr_set_block_header(xattr_ref); + block_data = (char *)block_header + block_size_rem; + block_size_rem -= sizeof(struct ext4_xattr_header); + + xattr_ref->block.dirty = true; + } else { + /* We don't need an extra block.*/ + if (xattr_ref->block_loaded) { + block_header = EXT4_XATTR_BHDR(&xattr_ref->block); + block_header->h_refcount = + to_le32(to_le32(block_header->h_refcount) - 1); + if (!block_header->h_refcount) { + ext4_xattr_try_free_block(xattr_ref); + block_header = NULL; + } else { + block_entry = + EXT4_XATTR_BFIRST(&xattr_ref->block); + block_data = + (char *)block_header + block_size_rem; + block_size_rem -= + sizeof(struct ext4_xattr_header); + ext4_inode_set_file_acl( + xattr_ref->inode_ref->inode, + &xattr_ref->fs->sb, 0); + + xattr_ref->inode_ref->dirty = true; + xattr_ref->block.dirty = true; + } + } + } + RB_FOREACH_SAFE(item, ext4_xattr_tree, &xattr_ref->root, save_item) + { + if (EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len) <= + inode_size_rem) { + ibody_data = (char *)ibody_data - + EXT4_XATTR_SIZE(item->data_size); + ext4_xattr_set_inode_entry(item, ibody_header, entry, + ibody_data); + memcpy(EXT4_XATTR_NAME(entry), item->name, + item->name_len); + memcpy(ibody_data, item->data, item->data_size); + entry = EXT4_XATTR_NEXT(entry); + inode_size_rem -= EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len); + + xattr_ref->inode_ref->dirty = true; + continue; + } + if (EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len) > + block_size_rem) { + ret = ENOSPC; + goto Finish; + } + block_data = + (char *)block_data - EXT4_XATTR_SIZE(item->data_size); + ext4_xattr_set_block_entry(item, block_header, block_entry, + block_data); + memcpy(EXT4_XATTR_NAME(block_entry), item->name, + item->name_len); + memcpy(block_data, item->data, item->data_size); + block_entry = EXT4_XATTR_NEXT(block_entry); + block_size_rem -= EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len); + + block_modified = true; + } + xattr_ref->dirty = false; + if (block_modified) { + ext4_xattr_rehash(block_header, + EXT4_XATTR_BFIRST(&xattr_ref->block)); + xattr_ref->block.dirty = true; + } + +Finish: + return ret; +} + +void ext4_fs_xattr_iterate(struct ext4_xattr_ref *ref, + int(iter)(struct ext4_xattr_ref *ref, + struct ext4_xattr_item *item)) +{ + struct ext4_xattr_item *item; + if (!ref->iter_from) + ref->iter_from = RB_MIN(ext4_xattr_tree, &ref->root); + + RB_FOREACH_FROM(item, ext4_xattr_tree, ref->iter_from) + { + int ret = EXT4_XATTR_ITERATE_CONT; + if (iter) + iter(ref, item); + + if (ret != EXT4_XATTR_ITERATE_CONT) { + if (ret == EXT4_XATTR_ITERATE_STOP) + ref->iter_from = NULL; + + break; + } + } +} + +void ext4_fs_xattr_iterate_reset(struct ext4_xattr_ref *ref) +{ + ref->iter_from = NULL; +} + +int ext4_fs_set_xattr(struct ext4_xattr_ref *ref, uint8_t name_index, + char *name, size_t name_len, void *data, size_t data_size, + bool replace) +{ + int ret = EOK; + struct ext4_xattr_item *item = + ext4_xattr_lookup_item(ref, name_index, name, name_len); + if (replace) { + if (!item) { + ret = ENOATTR; + goto Finish; + } + if (item->data_size != data_size) + ret = ext4_xattr_resize_item(ref, item, data_size); + + if (ret != EOK) { + goto Finish; + } + memcpy(item->data, data, data_size); + } else { + if (item) { + ret = EEXIST; + goto Finish; + } + item = ext4_xattr_insert_item(ref, name_index, name, name_len, + data, data_size); + if (!item) + ret = ENOMEM; + } +Finish: + return ret; +} + +int ext4_fs_remove_xattr(struct ext4_xattr_ref *ref, uint8_t name_index, + char *name, size_t name_len) +{ + return ext4_xattr_remove_item(ref, name_index, name, name_len); +} + +int ext4_fs_get_xattr(struct ext4_xattr_ref *ref, uint8_t name_index, + char *name, size_t name_len, void *buf, size_t buf_size, + size_t *size_got) +{ + int ret = EOK; + size_t item_size = 0; + struct ext4_xattr_item *item = + ext4_xattr_lookup_item(ref, name_index, name, name_len); + + if (!item) { + ret = ENOATTR; + goto Finish; + } + item_size = item->data_size; + if (buf_size > item_size) + buf_size = item_size; + + if (buf) + memcpy(buf, item->data, buf_size); + +Finish: + if (size_got) + *size_got = buf_size; + + return ret; +} + +int ext4_fs_get_xattr_ref(struct ext4_fs *fs, struct ext4_inode_ref *inode_ref, struct ext4_xattr_ref *ref) { int rc; uint64_t xattr_block; - xattr_block = ext4_inode_get_file_acl(inode_ref->inode, - &fs->sb); - memset(&ref->root, 0, sizeof(ref->root)); + xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + RB_INIT(&ref->root); + ref->ea_size = 0; + ref->iter_from = NULL; if (xattr_block) { - rc = ext4_block_get(fs->bdev, - &inode_ref->block, xattr_block); + rc = ext4_block_get(fs->bdev, &ref->block, xattr_block); if (rc != EOK) return EIO; - + + ref->ea_size += sizeof(struct ext4_xattr_header); ref->block_loaded = true; } else ref->block_loaded = false; @@ -343,6 +774,10 @@ int ext4_fs_get_xattr_ref(struct ext4_fs *fs, ref->inode_ref = inode_ref; ref->fs = fs; + if (ext4_xattr_inode_space(ref) > + sizeof(struct ext4_xattr_ibody_header)) + ref->ea_size += sizeof(struct ext4_xattr_ibody_header); + rc = ext4_xattr_fetch(ref); if (rc != EOK) { ext4_xattr_purge_items(ref); @@ -357,6 +792,7 @@ int ext4_fs_get_xattr_ref(struct ext4_fs *fs, void ext4_fs_put_xattr_ref(struct ext4_xattr_ref *ref) { + ext4_xattr_write_to_disk(ref); if (ref->block_loaded) { ext4_block_set(ref->fs->bdev, &ref->block); ref->block_loaded = false; @@ -366,3 +802,44 @@ void ext4_fs_put_xattr_ref(struct ext4_xattr_ref *ref) ref->fs = NULL; } +struct xattr_prefix { + char *prefix; + uint8_t name_index; +}; + +static const struct xattr_prefix prefix_tbl[] = { + {"user.", EXT4_XATTR_INDEX_USER}, + {"system.", EXT4_XATTR_INDEX_SYSTEM}, + {"system.posix_acl_access", EXT4_XATTR_INDEX_POSIX_ACL_ACCESS}, + {"system.posix_acl_default", EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT}, + {NULL, 0}, +}; + +char *ext4_extract_xattr_name(char *full_name, size_t full_name_len, + uint8_t *name_index, size_t *name_len) +{ + int i; + ext4_assert(name_index); + if (!full_name_len) + return NULL; + + for (i = 0; prefix_tbl[i].prefix; i++) { + size_t prefix_len = strlen(prefix_tbl[i].prefix); + if (full_name_len >= prefix_len && + !memcmp(full_name, prefix_tbl[i].prefix, prefix_len)) { + *name_index = prefix_tbl[i].name_index; + if (name_len) + *name_len = full_name_len - prefix_len; + + return full_name + prefix_len; + } + } + if (name_len) + *name_len = 0; + + return NULL; +} + +/** + * @} + */