/** * xattr.c * * Many parts of codes are copied from Linux kernel/fs/f2fs. * * Copyright (C) 2015 Huawei Ltd. * Witten by: * Hou Pengyang * Liu Shuoran * Jaegeuk Kim * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include "fsck.h" #include "node.h" #include "xattr.h" #define XATTR_CREATE 0x1 #define XATTR_REPLACE 0x2 static void *read_all_xattrs(struct f2fs_sb_info *sbi, struct f2fs_node *inode) { struct f2fs_xattr_header *header; void *txattr_addr; u64 inline_size = inline_xattr_size(&inode->i); txattr_addr = calloc(inline_size + BLOCK_SZ, 1); ASSERT(txattr_addr); if (inline_size) memcpy(txattr_addr, inline_xattr_addr(&inode->i), inline_size); /* Read from xattr node block. */ if (inode->i.i_xattr_nid) { struct node_info ni; int ret; get_node_info(sbi, le32_to_cpu(inode->i.i_xattr_nid), &ni); ret = dev_read_block(txattr_addr + inline_size, ni.blk_addr); ASSERT(ret >= 0); } header = XATTR_HDR(txattr_addr); /* Never been allocated xattrs */ if (le32_to_cpu(header->h_magic) != F2FS_XATTR_MAGIC) { header->h_magic = cpu_to_le32(F2FS_XATTR_MAGIC); header->h_refcount = cpu_to_le32(1); } return txattr_addr; } static struct f2fs_xattr_entry *__find_xattr(void *base_addr, int index, size_t len, const char *name) { struct f2fs_xattr_entry *entry; list_for_each_xattr(entry, base_addr) { if (entry->e_name_index != index) continue; if (entry->e_name_len != len) continue; if (!memcmp(entry->e_name, name, len)) break; } return entry; } static void write_all_xattrs(struct f2fs_sb_info *sbi, struct f2fs_node *inode, __u32 hsize, void *txattr_addr) { void *xattr_addr; struct dnode_of_data dn; struct node_info ni; struct f2fs_node *xattr_node; nid_t new_nid = 0; block_t blkaddr; nid_t xnid = le32_to_cpu(inode->i.i_xattr_nid); u64 inline_size = inline_xattr_size(&inode->i); int ret; ASSERT(inode->i.i_inline & F2FS_INLINE_XATTR); memcpy(inline_xattr_addr(&inode->i), txattr_addr, inline_size); if (hsize <= inline_size) return; if (!xnid) { f2fs_alloc_nid(sbi, &new_nid, 0); set_new_dnode(&dn, inode, NULL, new_nid); /* NAT entry would be updated by new_node_page. */ blkaddr = new_node_block(sbi, &dn, XATTR_NODE_OFFSET); ASSERT(dn.node_blk); xattr_node = dn.node_blk; inode->i.i_xattr_nid = cpu_to_le32(new_nid); } else { set_new_dnode(&dn, inode, NULL, xnid); get_node_info(sbi, xnid, &ni); blkaddr = ni.blk_addr; xattr_node = calloc(BLOCK_SZ, 1); ASSERT(xattr_node); ret = dev_read_block(xattr_node, ni.blk_addr); ASSERT(ret >= 0); } /* write to xattr node block */ xattr_addr = (void *)xattr_node; memcpy(xattr_addr, txattr_addr + inline_size, PAGE_SIZE - sizeof(struct node_footer)); ret = dev_write_block(xattr_node, blkaddr); ASSERT(ret >= 0); } int f2fs_setxattr(struct f2fs_sb_info *sbi, nid_t ino, int index, const char *name, const void *value, size_t size, int flags) { struct f2fs_node *inode; void *base_addr; struct f2fs_xattr_entry *here, *last; struct node_info ni; int error = 0; int len; int found, newsize; __u32 new_hsize; int ret; if (name == NULL) return -EINVAL; if (value == NULL) return -EINVAL; len = strlen(name); if (len > F2FS_NAME_LEN || size > MAX_VALUE_LEN) return -ERANGE; if (ino < 3) return -EINVAL; /* Now We just support selinux */ ASSERT(index == F2FS_XATTR_INDEX_SECURITY); get_node_info(sbi, ino, &ni); inode = calloc(BLOCK_SZ, 1); ASSERT(inode); ret = dev_read_block(inode, ni.blk_addr); ASSERT(ret >= 0); base_addr = read_all_xattrs(sbi, inode); ASSERT(base_addr); here = __find_xattr(base_addr, index, len, name); found = IS_XATTR_LAST_ENTRY(here) ? 0 : 1; if ((flags & XATTR_REPLACE) && !found) { error = -ENODATA; goto exit; } else if ((flags & XATTR_CREATE) && found) { error = -EEXIST; goto exit; } last = here; while (!IS_XATTR_LAST_ENTRY(last)) last = XATTR_NEXT_ENTRY(last); newsize = XATTR_ALIGN(sizeof(struct f2fs_xattr_entry) + len + size); /* 1. Check space */ if (value) { int free; /* * If value is NULL, it is remove operation. * In case of update operation, we calculate free. */ free = MIN_OFFSET - ((char *)last - (char *)base_addr); if (found) free = free + ENTRY_SIZE(here); if (free < newsize) { error = -ENOSPC; goto exit; } } /* 2. Remove old entry */ if (found) { /* * If entry if sound, remove old entry. * If not found, remove operation is not needed */ struct f2fs_xattr_entry *next = XATTR_NEXT_ENTRY(here); int oldsize = ENTRY_SIZE(here); memmove(here, next, (char *)last - (char *)next); last = (struct f2fs_xattr_entry *)((char *)last - oldsize); memset(last, 0, oldsize); } new_hsize = (char *)last - (char *)base_addr; /* 3. Write new entry */ if (value) { char *pval; /* * Before we come here, old entry is removed. * We just write new entry. */ memset(last, 0, newsize); last->e_name_index = index; last->e_name_len = len; memcpy(last->e_name, name, len); pval = last->e_name + len; memcpy(pval, value, size); last->e_value_size = cpu_to_le16(size); new_hsize += newsize; } write_all_xattrs(sbi, inode, new_hsize, base_addr); /* inode need update */ ret = dev_write_block(inode, ni.blk_addr); ASSERT(ret >= 0); exit: free(base_addr); return error; } int inode_set_selinux(struct f2fs_sb_info *sbi, u32 ino, const char *secon) { if (!secon) return 0; return f2fs_setxattr(sbi, ino, F2FS_XATTR_INDEX_SECURITY, XATTR_SELINUX_SUFFIX, secon, strlen(secon), 1); }