+/**
+ * @brief Remove an EA entry from a xattr block
+ *
+ * @param inode_ref Inode reference
+ * @param i The information of the given EA entry
+ *
+ * @return Error code
+ */
+static int ext4_xattr_block_remove(struct ext4_inode_ref *inode_ref,
+ struct ext4_xattr_info *i)
+{
+ int ret = EOK;
+ bool allocated = false;
+ const void *value = i->value;
+ struct ext4_fs *fs = inode_ref->fs;
+ struct ext4_xattr_finder finder;
+ struct ext4_block block, new_block;
+ struct ext4_xattr_header *header;
+ ext4_fsblk_t orig_xattr_block;
+ orig_xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb);
+
+ ext4_assert(orig_xattr_block);
+ ret = ext4_trans_block_get(fs->bdev, &block, orig_xattr_block);
+ if (ret != EOK)
+ goto out;
+
+ /*
+ * There will be no effect when the xattr block is only referenced
+ * once.
+ */
+ ret = ext4_xattr_copy_new_block(inode_ref, &block, &new_block,
+ &orig_xattr_block, &allocated);
+ if (ret != EOK) {
+ ext4_block_set(fs->bdev, &block);
+ goto out;
+ }
+
+ if (allocated) {
+ ext4_block_set(fs->bdev, &block);
+ block = new_block;
+ }
+
+ ext4_xattr_block_find_entry(inode_ref, &finder, &block);
+
+ if (!finder.s.not_found) {
+ i->value = NULL;
+ ret = ext4_xattr_set_entry(i, &finder.s, false);
+ i->value = value;
+
+ header = EXT4_XATTR_BHDR(&block);
+ ext4_assert(finder.s.first);
+ ext4_xattr_rehash(header, finder.s.first);
+ ext4_xattr_set_block_checksum(inode_ref,
+ block.lb_id,
+ header);
+ ext4_trans_set_block_dirty(block.buf);
+ }
+
+ ext4_block_set(fs->bdev, &block);
+out:
+ return ret;
+}
+
+/**
+ * @brief Insert an EA entry into a given inode reference
+ *
+ * @param inode_ref Inode reference
+ * @param name_index Name-index
+ * @param name Name of the EA entry to be inserted
+ * @param name_len Length of name in bytes
+ * @param value Input buffer to hold content
+ * @param value_len Length of input content
+ *
+ * @return Error code
+ */
+int ext4_xattr_set(struct ext4_inode_ref *inode_ref, uint8_t name_index,
+ const char *name, size_t name_len, const void *value,
+ size_t value_len)
+{
+ int ret = EOK;
+ struct ext4_fs *fs = inode_ref->fs;
+ struct ext4_xattr_finder ibody_finder;
+ struct ext4_xattr_info i;
+ bool block_found = false;
+ ext4_fsblk_t orig_xattr_block;
+ size_t extra_isize =
+ ext4_inode_get_extra_isize(&fs->sb, inode_ref->inode);
+
+ i.name_index = name_index;
+ i.name = name;
+ i.name_len = name_len;
+ i.value = (value_len) ? value : &ext4_xattr_empty_value;
+ i.value_len = value_len;
+
+ ibody_finder.i = i;
+
+ orig_xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb);
+
+ /*
+ * Even if entry is not found, search context block inside the
+ * finder is still valid and can be used to insert entry.
+ */
+ ret = ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder);
+ if (ret != EOK) {
+ ext4_xattr_ibody_initialize(inode_ref);
+ ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder);
+ }
+
+ if (ibody_finder.s.not_found) {
+ if (orig_xattr_block) {
+ block_found = true;
+ ret = ext4_xattr_block_set(inode_ref, &i, true);
+ if (ret == ENOSPC)
+ goto try_insert;
+ else if (ret == ENODATA)
+ goto try_insert;
+ else if (ret != EOK)
+ goto out;
+
+ } else
+ goto try_insert;
+
+ } else {
+ try_insert:
+ /* Only try to set entry in ibody if inode is sufficiently large */
+ if (extra_isize)
+ ret = ext4_xattr_set_entry(&i, &ibody_finder.s, false);
+ else
+ ret = ENOSPC;
+
+ if (ret == ENOSPC) {
+ if (!block_found) {
+ ret = ext4_xattr_block_set(inode_ref, &i, false);
+ ibody_finder.i.value = NULL;
+ ext4_xattr_set_entry(&ibody_finder.i,
+ &ibody_finder.s, false);
+ inode_ref->dirty = true;
+ }
+
+ } else if (ret == EOK) {
+ if (block_found)
+ ret = ext4_xattr_block_remove(inode_ref, &i);
+
+ inode_ref->dirty = true;
+ }
+ }
+
+out:
+ return ret;