// SPDX-License-Identifier: GPL-2.0 /* * linux/fs/ext2/xattr.c * * Copyright (C) 2001-2003 Andreas Gruenbacher <[email protected]> * * Fix by Harrison Xing <[email protected]>. * Extended attributes for symlinks and special files added per * suggestion of Luka Renko <[email protected]>. * xattr consolidation Copyright (c) 2004 James Morris <[email protected]>, * Red Hat Inc. * */ /* * Extended attributes are stored on disk blocks allocated outside of * any inode. The i_file_acl field is then made to point to this allocated * block. If all extended attributes of an inode are identical, these * inodes may share the same extended attribute block. Such situations * are automatically detected by keeping a cache of recent attribute block * numbers and hashes over the block's contents in memory. * * * Extended attribute block layout: * * +------------------+ * | header | * | entry 1 | | * | entry 2 | | growing downwards * | entry 3 | v * | four null bytes | * | . . . | * | value 1 | ^ * | value 3 | | growing upwards * | value 2 | | * +------------------+ * * The block header is followed by multiple entry descriptors. These entry * descriptors are variable in size, and aligned to EXT2_XATTR_PAD * byte boundaries. The entry descriptors are sorted by attribute name, * so that two extended attribute blocks can be compared efficiently. * * Attribute values are aligned to the end of the block, stored in * no specific order. They are also padded to EXT2_XATTR_PAD byte * boundaries. No additional gaps are left between them. * * Locking strategy * ---------------- * EXT2_I(inode)->i_file_acl is protected by EXT2_I(inode)->xattr_sem. * EA blocks are only changed if they are exclusive to an inode, so * holding xattr_sem also means that nothing but the EA block's reference * count will change. Multiple writers to an EA block are synchronized * by the bh lock. No more than a single bh lock is held at any time * to avoid deadlocks. */ #include <linux/buffer_head.h> #include <linux/init.h> #include <linux/printk.h> #include <linux/slab.h> #include <linux/mbcache.h> #include <linux/quotaops.h> #include <linux/rwsem.h> #include <linux/security.h> #include "ext2.h" #include "xattr.h" #include "acl.h" #define HDR(bh) … #define ENTRY(ptr) … #define FIRST_ENTRY(bh) … #define IS_LAST_ENTRY(entry) … #ifdef EXT2_XATTR_DEBUG #define ea_idebug … #define ea_bdebug … #else #define ea_idebug(inode, f...) … #define ea_bdebug(bh, f...) … #endif static int ext2_xattr_set2(struct inode *, struct buffer_head *, struct ext2_xattr_header *); static int ext2_xattr_cache_insert(struct mb_cache *, struct buffer_head *); static struct buffer_head *ext2_xattr_cache_find(struct inode *, struct ext2_xattr_header *); static void ext2_xattr_rehash(struct ext2_xattr_header *, struct ext2_xattr_entry *); static const struct xattr_handler * const ext2_xattr_handler_map[] = …; const struct xattr_handler * const ext2_xattr_handlers[] = …; #define EA_BLOCK_CACHE(inode) … static inline const char *ext2_xattr_prefix(int name_index, struct dentry *dentry) { … } static bool ext2_xattr_header_valid(struct ext2_xattr_header *header) { … } static bool ext2_xattr_entry_valid(struct ext2_xattr_entry *entry, char *end, size_t end_offs) { … } static int ext2_xattr_cmp_entry(int name_index, size_t name_len, const char *name, struct ext2_xattr_entry *entry) { … } /* * ext2_xattr_get() * * Copy an extended attribute into the buffer * provided, or compute the buffer size required. * Buffer is NULL to compute the size of the buffer required. * * Returns a negative error number on failure, or the number of bytes * used / required on success. */ int ext2_xattr_get(struct inode *inode, int name_index, const char *name, void *buffer, size_t buffer_size) { … } /* * ext2_xattr_list() * * Copy a list of attribute names into the buffer * provided, or compute the buffer size required. * Buffer is NULL to compute the size of the buffer required. * * Returns a negative error number on failure, or the number of bytes * used / required on success. */ static int ext2_xattr_list(struct dentry *dentry, char *buffer, size_t buffer_size) { … } /* * Inode operation listxattr() * * d_inode(dentry)->i_mutex: don't care */ ssize_t ext2_listxattr(struct dentry *dentry, char *buffer, size_t size) { … } /* * If the EXT2_FEATURE_COMPAT_EXT_ATTR feature of this file system is * not set, set it. */ static void ext2_xattr_update_super_block(struct super_block *sb) { … } /* * ext2_xattr_set() * * Create, replace or remove an extended attribute for this inode. Value * is NULL to remove an existing extended attribute, and non-NULL to * either replace an existing extended attribute, or create a new extended * attribute. The flags XATTR_REPLACE and XATTR_CREATE * specify that an extended attribute must exist and must not exist * previous to the call, respectively. * * Returns 0, or a negative error number on failure. */ int ext2_xattr_set(struct inode *inode, int name_index, const char *name, const void *value, size_t value_len, int flags) { … } static void ext2_xattr_release_block(struct inode *inode, struct buffer_head *bh) { … } /* * Second half of ext2_xattr_set(): Update the file system. */ static int ext2_xattr_set2(struct inode *inode, struct buffer_head *old_bh, struct ext2_xattr_header *header) { … } /* * ext2_xattr_delete_inode() * * Free extended attribute resources associated with this inode. This * is called immediately before an inode is freed. */ void ext2_xattr_delete_inode(struct inode *inode) { … } /* * ext2_xattr_cache_insert() * * Create a new entry in the extended attribute cache, and insert * it unless such an entry is already in the cache. * * Returns 0, or a negative error number on failure. */ static int ext2_xattr_cache_insert(struct mb_cache *cache, struct buffer_head *bh) { … } /* * ext2_xattr_cmp() * * Compare two extended attribute blocks for equality. * * Returns 0 if the blocks are equal, 1 if they differ, and * a negative error number on errors. */ static int ext2_xattr_cmp(struct ext2_xattr_header *header1, struct ext2_xattr_header *header2) { … } /* * ext2_xattr_cache_find() * * Find an identical extended attribute block. * * Returns a locked buffer head to the block found, or NULL if such * a block was not found or an error occurred. */ static struct buffer_head * ext2_xattr_cache_find(struct inode *inode, struct ext2_xattr_header *header) { … } #define NAME_HASH_SHIFT … #define VALUE_HASH_SHIFT … /* * ext2_xattr_hash_entry() * * Compute the hash of an extended attribute. */ static inline void ext2_xattr_hash_entry(struct ext2_xattr_header *header, struct ext2_xattr_entry *entry) { … } #undef NAME_HASH_SHIFT #undef VALUE_HASH_SHIFT #define BLOCK_HASH_SHIFT … /* * ext2_xattr_rehash() * * Re-compute the extended attribute hash value after an entry has changed. */ static void ext2_xattr_rehash(struct ext2_xattr_header *header, struct ext2_xattr_entry *entry) { … } #undef BLOCK_HASH_SHIFT #define HASH_BUCKET_BITS … struct mb_cache *ext2_xattr_create_cache(void) { … } void ext2_xattr_destroy_cache(struct mb_cache *cache) { … }