mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-10-31 08:44:41 +00:00 
			
		
		
		
	 7f2fc81ea2
			
		
	
	
		7f2fc81ea2
		
	
	
	
	
		
			
			The directory/file catalog b-tree equivalent, hfsplus_build_key_uni(), is used by hfsplus_find_cat() for internal referencing between catalog records. There is no corresponding usage for attributes - attribute records do not refer to one another. Signed-off-by: Hin-Tak Leung <htl10@users.sourceforge.net> Cc: Sougata Santra <sougata@tuxera.com> Cc: Anton Altaparmakov <anton@tuxera.com> Cc: Vyacheslav Dubeyko <slava@dubeyko.com> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Christoph Hellwig <hch@infradead.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
		
			
				
	
	
		
			371 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			371 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * linux/fs/hfsplus/attributes.c
 | |
|  *
 | |
|  * Vyacheslav Dubeyko <slava@dubeyko.com>
 | |
|  *
 | |
|  * Handling of records in attributes tree
 | |
|  */
 | |
| 
 | |
| #include "hfsplus_fs.h"
 | |
| #include "hfsplus_raw.h"
 | |
| 
 | |
| static struct kmem_cache *hfsplus_attr_tree_cachep;
 | |
| 
 | |
| int __init hfsplus_create_attr_tree_cache(void)
 | |
| {
 | |
| 	if (hfsplus_attr_tree_cachep)
 | |
| 		return -EEXIST;
 | |
| 
 | |
| 	hfsplus_attr_tree_cachep =
 | |
| 		kmem_cache_create("hfsplus_attr_cache",
 | |
| 			sizeof(hfsplus_attr_entry), 0,
 | |
| 			SLAB_HWCACHE_ALIGN, NULL);
 | |
| 	if (!hfsplus_attr_tree_cachep)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void hfsplus_destroy_attr_tree_cache(void)
 | |
| {
 | |
| 	kmem_cache_destroy(hfsplus_attr_tree_cachep);
 | |
| }
 | |
| 
 | |
| int hfsplus_attr_bin_cmp_key(const hfsplus_btree_key *k1,
 | |
| 				const hfsplus_btree_key *k2)
 | |
| {
 | |
| 	__be32 k1_cnid, k2_cnid;
 | |
| 
 | |
| 	k1_cnid = k1->attr.cnid;
 | |
| 	k2_cnid = k2->attr.cnid;
 | |
| 	if (k1_cnid != k2_cnid)
 | |
| 		return be32_to_cpu(k1_cnid) < be32_to_cpu(k2_cnid) ? -1 : 1;
 | |
| 
 | |
| 	return hfsplus_strcmp(
 | |
| 			(const struct hfsplus_unistr *)&k1->attr.key_name,
 | |
| 			(const struct hfsplus_unistr *)&k2->attr.key_name);
 | |
| }
 | |
| 
 | |
| int hfsplus_attr_build_key(struct super_block *sb, hfsplus_btree_key *key,
 | |
| 			u32 cnid, const char *name)
 | |
| {
 | |
| 	int len;
 | |
| 
 | |
| 	memset(key, 0, sizeof(struct hfsplus_attr_key));
 | |
| 	key->attr.cnid = cpu_to_be32(cnid);
 | |
| 	if (name) {
 | |
| 		int res = hfsplus_asc2uni(sb,
 | |
| 				(struct hfsplus_unistr *)&key->attr.key_name,
 | |
| 				HFSPLUS_ATTR_MAX_STRLEN, name, strlen(name));
 | |
| 		if (res)
 | |
| 			return res;
 | |
| 		len = be16_to_cpu(key->attr.key_name.length);
 | |
| 	} else {
 | |
| 		key->attr.key_name.length = 0;
 | |
| 		len = 0;
 | |
| 	}
 | |
| 
 | |
| 	/* The length of the key, as stored in key_len field, does not include
 | |
| 	 * the size of the key_len field itself.
 | |
| 	 * So, offsetof(hfsplus_attr_key, key_name) is a trick because
 | |
| 	 * it takes into consideration key_len field (__be16) of
 | |
| 	 * hfsplus_attr_key structure instead of length field (__be16) of
 | |
| 	 * hfsplus_attr_unistr structure.
 | |
| 	 */
 | |
| 	key->key_len =
 | |
| 		cpu_to_be16(offsetof(struct hfsplus_attr_key, key_name) +
 | |
| 				2 * len);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| hfsplus_attr_entry *hfsplus_alloc_attr_entry(void)
 | |
| {
 | |
| 	return kmem_cache_alloc(hfsplus_attr_tree_cachep, GFP_KERNEL);
 | |
| }
 | |
| 
 | |
| void hfsplus_destroy_attr_entry(hfsplus_attr_entry *entry)
 | |
| {
 | |
| 	if (entry)
 | |
| 		kmem_cache_free(hfsplus_attr_tree_cachep, entry);
 | |
| }
 | |
| 
 | |
| #define HFSPLUS_INVALID_ATTR_RECORD -1
 | |
| 
 | |
| static int hfsplus_attr_build_record(hfsplus_attr_entry *entry, int record_type,
 | |
| 				u32 cnid, const void *value, size_t size)
 | |
| {
 | |
| 	if (record_type == HFSPLUS_ATTR_FORK_DATA) {
 | |
| 		/*
 | |
| 		 * Mac OS X supports only inline data attributes.
 | |
| 		 * Do nothing
 | |
| 		 */
 | |
| 		memset(entry, 0, sizeof(*entry));
 | |
| 		return sizeof(struct hfsplus_attr_fork_data);
 | |
| 	} else if (record_type == HFSPLUS_ATTR_EXTENTS) {
 | |
| 		/*
 | |
| 		 * Mac OS X supports only inline data attributes.
 | |
| 		 * Do nothing.
 | |
| 		 */
 | |
| 		memset(entry, 0, sizeof(*entry));
 | |
| 		return sizeof(struct hfsplus_attr_extents);
 | |
| 	} else if (record_type == HFSPLUS_ATTR_INLINE_DATA) {
 | |
| 		u16 len;
 | |
| 
 | |
| 		memset(entry, 0, sizeof(struct hfsplus_attr_inline_data));
 | |
| 		entry->inline_data.record_type = cpu_to_be32(record_type);
 | |
| 		if (size <= HFSPLUS_MAX_INLINE_DATA_SIZE)
 | |
| 			len = size;
 | |
| 		else
 | |
| 			return HFSPLUS_INVALID_ATTR_RECORD;
 | |
| 		entry->inline_data.length = cpu_to_be16(len);
 | |
| 		memcpy(entry->inline_data.raw_bytes, value, len);
 | |
| 		/*
 | |
| 		 * Align len on two-byte boundary.
 | |
| 		 * It needs to add pad byte if we have odd len.
 | |
| 		 */
 | |
| 		len = round_up(len, 2);
 | |
| 		return offsetof(struct hfsplus_attr_inline_data, raw_bytes) +
 | |
| 					len;
 | |
| 	} else /* invalid input */
 | |
| 		memset(entry, 0, sizeof(*entry));
 | |
| 
 | |
| 	return HFSPLUS_INVALID_ATTR_RECORD;
 | |
| }
 | |
| 
 | |
| int hfsplus_find_attr(struct super_block *sb, u32 cnid,
 | |
| 			const char *name, struct hfs_find_data *fd)
 | |
| {
 | |
| 	int err = 0;
 | |
| 
 | |
| 	hfs_dbg(ATTR_MOD, "find_attr: %s,%d\n", name ? name : NULL, cnid);
 | |
| 
 | |
| 	if (!HFSPLUS_SB(sb)->attr_tree) {
 | |
| 		pr_err("attributes file doesn't exist\n");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (name) {
 | |
| 		err = hfsplus_attr_build_key(sb, fd->search_key, cnid, name);
 | |
| 		if (err)
 | |
| 			goto failed_find_attr;
 | |
| 		err = hfs_brec_find(fd, hfs_find_rec_by_key);
 | |
| 		if (err)
 | |
| 			goto failed_find_attr;
 | |
| 	} else {
 | |
| 		err = hfsplus_attr_build_key(sb, fd->search_key, cnid, NULL);
 | |
| 		if (err)
 | |
| 			goto failed_find_attr;
 | |
| 		err = hfs_brec_find(fd, hfs_find_1st_rec_by_cnid);
 | |
| 		if (err)
 | |
| 			goto failed_find_attr;
 | |
| 	}
 | |
| 
 | |
| failed_find_attr:
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| int hfsplus_attr_exists(struct inode *inode, const char *name)
 | |
| {
 | |
| 	int err = 0;
 | |
| 	struct super_block *sb = inode->i_sb;
 | |
| 	struct hfs_find_data fd;
 | |
| 
 | |
| 	if (!HFSPLUS_SB(sb)->attr_tree)
 | |
| 		return 0;
 | |
| 
 | |
| 	err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
 | |
| 	if (err)
 | |
| 		return 0;
 | |
| 
 | |
| 	err = hfsplus_find_attr(sb, inode->i_ino, name, &fd);
 | |
| 	if (err)
 | |
| 		goto attr_not_found;
 | |
| 
 | |
| 	hfs_find_exit(&fd);
 | |
| 	return 1;
 | |
| 
 | |
| attr_not_found:
 | |
| 	hfs_find_exit(&fd);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int hfsplus_create_attr(struct inode *inode,
 | |
| 				const char *name,
 | |
| 				const void *value, size_t size)
 | |
| {
 | |
| 	struct super_block *sb = inode->i_sb;
 | |
| 	struct hfs_find_data fd;
 | |
| 	hfsplus_attr_entry *entry_ptr;
 | |
| 	int entry_size;
 | |
| 	int err;
 | |
| 
 | |
| 	hfs_dbg(ATTR_MOD, "create_attr: %s,%ld\n",
 | |
| 		name ? name : NULL, inode->i_ino);
 | |
| 
 | |
| 	if (!HFSPLUS_SB(sb)->attr_tree) {
 | |
| 		pr_err("attributes file doesn't exist\n");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	entry_ptr = hfsplus_alloc_attr_entry();
 | |
| 	if (!entry_ptr)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
 | |
| 	if (err)
 | |
| 		goto failed_init_create_attr;
 | |
| 
 | |
| 	if (name) {
 | |
| 		err = hfsplus_attr_build_key(sb, fd.search_key,
 | |
| 						inode->i_ino, name);
 | |
| 		if (err)
 | |
| 			goto failed_create_attr;
 | |
| 	} else {
 | |
| 		err = -EINVAL;
 | |
| 		goto failed_create_attr;
 | |
| 	}
 | |
| 
 | |
| 	/* Mac OS X supports only inline data attributes. */
 | |
| 	entry_size = hfsplus_attr_build_record(entry_ptr,
 | |
| 					HFSPLUS_ATTR_INLINE_DATA,
 | |
| 					inode->i_ino,
 | |
| 					value, size);
 | |
| 	if (entry_size == HFSPLUS_INVALID_ATTR_RECORD) {
 | |
| 		err = -EINVAL;
 | |
| 		goto failed_create_attr;
 | |
| 	}
 | |
| 
 | |
| 	err = hfs_brec_find(&fd, hfs_find_rec_by_key);
 | |
| 	if (err != -ENOENT) {
 | |
| 		if (!err)
 | |
| 			err = -EEXIST;
 | |
| 		goto failed_create_attr;
 | |
| 	}
 | |
| 
 | |
| 	err = hfs_brec_insert(&fd, entry_ptr, entry_size);
 | |
| 	if (err)
 | |
| 		goto failed_create_attr;
 | |
| 
 | |
| 	hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
 | |
| 
 | |
| failed_create_attr:
 | |
| 	hfs_find_exit(&fd);
 | |
| 
 | |
| failed_init_create_attr:
 | |
| 	hfsplus_destroy_attr_entry(entry_ptr);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static int __hfsplus_delete_attr(struct inode *inode, u32 cnid,
 | |
| 					struct hfs_find_data *fd)
 | |
| {
 | |
| 	int err = 0;
 | |
| 	__be32 found_cnid, record_type;
 | |
| 
 | |
| 	hfs_bnode_read(fd->bnode, &found_cnid,
 | |
| 			fd->keyoffset +
 | |
| 			offsetof(struct hfsplus_attr_key, cnid),
 | |
| 			sizeof(__be32));
 | |
| 	if (cnid != be32_to_cpu(found_cnid))
 | |
| 		return -ENOENT;
 | |
| 
 | |
| 	hfs_bnode_read(fd->bnode, &record_type,
 | |
| 			fd->entryoffset, sizeof(record_type));
 | |
| 
 | |
| 	switch (be32_to_cpu(record_type)) {
 | |
| 	case HFSPLUS_ATTR_INLINE_DATA:
 | |
| 		/* All is OK. Do nothing. */
 | |
| 		break;
 | |
| 	case HFSPLUS_ATTR_FORK_DATA:
 | |
| 	case HFSPLUS_ATTR_EXTENTS:
 | |
| 		pr_err("only inline data xattr are supported\n");
 | |
| 		return -EOPNOTSUPP;
 | |
| 	default:
 | |
| 		pr_err("invalid extended attribute record\n");
 | |
| 		return -ENOENT;
 | |
| 	}
 | |
| 
 | |
| 	err = hfs_brec_remove(fd);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 
 | |
| 	hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| int hfsplus_delete_attr(struct inode *inode, const char *name)
 | |
| {
 | |
| 	int err = 0;
 | |
| 	struct super_block *sb = inode->i_sb;
 | |
| 	struct hfs_find_data fd;
 | |
| 
 | |
| 	hfs_dbg(ATTR_MOD, "delete_attr: %s,%ld\n",
 | |
| 		name ? name : NULL, inode->i_ino);
 | |
| 
 | |
| 	if (!HFSPLUS_SB(sb)->attr_tree) {
 | |
| 		pr_err("attributes file doesn't exist\n");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 
 | |
| 	if (name) {
 | |
| 		err = hfsplus_attr_build_key(sb, fd.search_key,
 | |
| 						inode->i_ino, name);
 | |
| 		if (err)
 | |
| 			goto out;
 | |
| 	} else {
 | |
| 		pr_err("invalid extended attribute name\n");
 | |
| 		err = -EINVAL;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	err = hfs_brec_find(&fd, hfs_find_rec_by_key);
 | |
| 	if (err)
 | |
| 		goto out;
 | |
| 
 | |
| 	err = __hfsplus_delete_attr(inode, inode->i_ino, &fd);
 | |
| 	if (err)
 | |
| 		goto out;
 | |
| 
 | |
| out:
 | |
| 	hfs_find_exit(&fd);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| int hfsplus_delete_all_attrs(struct inode *dir, u32 cnid)
 | |
| {
 | |
| 	int err = 0;
 | |
| 	struct hfs_find_data fd;
 | |
| 
 | |
| 	hfs_dbg(ATTR_MOD, "delete_all_attrs: %d\n", cnid);
 | |
| 
 | |
| 	if (!HFSPLUS_SB(dir->i_sb)->attr_tree) {
 | |
| 		pr_err("attributes file doesn't exist\n");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	err = hfs_find_init(HFSPLUS_SB(dir->i_sb)->attr_tree, &fd);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 
 | |
| 	for (;;) {
 | |
| 		err = hfsplus_find_attr(dir->i_sb, cnid, NULL, &fd);
 | |
| 		if (err) {
 | |
| 			if (err != -ENOENT)
 | |
| 				pr_err("xattr search failed\n");
 | |
| 			goto end_delete_all;
 | |
| 		}
 | |
| 
 | |
| 		err = __hfsplus_delete_attr(dir, cnid, &fd);
 | |
| 		if (err)
 | |
| 			goto end_delete_all;
 | |
| 	}
 | |
| 
 | |
| end_delete_all:
 | |
| 	hfs_find_exit(&fd);
 | |
| 	return err;
 | |
| }
 |