diff options
Diffstat (limited to 'fs/hfsplus')
| -rw-r--r-- | fs/hfsplus/Kconfig | 18 | ||||
| -rw-r--r-- | fs/hfsplus/Makefile | 4 | ||||
| -rw-r--r-- | fs/hfsplus/acl.h | 27 | ||||
| -rw-r--r-- | fs/hfsplus/attributes.c | 371 | ||||
| -rw-r--r-- | fs/hfsplus/bfind.c | 105 | ||||
| -rw-r--r-- | fs/hfsplus/bitmap.c | 29 | ||||
| -rw-r--r-- | fs/hfsplus/bnode.c | 141 | ||||
| -rw-r--r-- | fs/hfsplus/brec.c | 59 | ||||
| -rw-r--r-- | fs/hfsplus/btree.c | 177 | ||||
| -rw-r--r-- | fs/hfsplus/catalog.c | 187 | ||||
| -rw-r--r-- | fs/hfsplus/dir.c | 226 | ||||
| -rw-r--r-- | fs/hfsplus/extents.c | 216 | ||||
| -rw-r--r-- | fs/hfsplus/hfsplus_fs.h | 355 | ||||
| -rw-r--r-- | fs/hfsplus/hfsplus_raw.h | 91 | ||||
| -rw-r--r-- | fs/hfsplus/inode.c | 256 | ||||
| -rw-r--r-- | fs/hfsplus/ioctl.c | 157 | ||||
| -rw-r--r-- | fs/hfsplus/options.c | 78 | ||||
| -rw-r--r-- | fs/hfsplus/part_tbl.c | 135 | ||||
| -rw-r--r-- | fs/hfsplus/posix_acl.c | 140 | ||||
| -rw-r--r-- | fs/hfsplus/super.c | 411 | ||||
| -rw-r--r-- | fs/hfsplus/unicode.c | 95 | ||||
| -rw-r--r-- | fs/hfsplus/wrapper.c | 218 | ||||
| -rw-r--r-- | fs/hfsplus/xattr.c | 875 | ||||
| -rw-r--r-- | fs/hfsplus/xattr.h | 49 | ||||
| -rw-r--r-- | fs/hfsplus/xattr_security.c | 124 | ||||
| -rw-r--r-- | fs/hfsplus/xattr_trusted.c | 71 | ||||
| -rw-r--r-- | fs/hfsplus/xattr_user.c | 71 | 
27 files changed, 3703 insertions, 983 deletions
diff --git a/fs/hfsplus/Kconfig b/fs/hfsplus/Kconfig index a63371815aa..24bc20fd42f 100644 --- a/fs/hfsplus/Kconfig +++ b/fs/hfsplus/Kconfig @@ -11,3 +11,21 @@ config HFSPLUS_FS  	  MacOS 8. It includes all Mac specific filesystem data such as  	  data forks and creator codes, but it also has several UNIX  	  style features such as file ownership and permissions. + +config HFSPLUS_FS_POSIX_ACL +	bool "HFS+ POSIX Access Control Lists" +	depends on HFSPLUS_FS +	select FS_POSIX_ACL +	help +	  POSIX Access Control Lists (ACLs) support permissions for users and +	  groups beyond the owner/group/world scheme. + +	  To learn more about Access Control Lists, visit the POSIX ACLs for +	  Linux website <http://acl.bestbits.at/>. + +	  It needs to understand that POSIX ACLs are treated only under +	  Linux. POSIX ACLs doesn't mean something under Mac OS X. +	  Mac OS X beginning with version 10.4 ("Tiger") support NFSv4 ACLs, +	  which are part of the NFSv4 standard. + +	  If you don't know what Access Control Lists are, say N diff --git a/fs/hfsplus/Makefile b/fs/hfsplus/Makefile index 3cc0df73015..683fca2e5e6 100644 --- a/fs/hfsplus/Makefile +++ b/fs/hfsplus/Makefile @@ -5,5 +5,7 @@  obj-$(CONFIG_HFSPLUS_FS) += hfsplus.o  hfsplus-objs := super.o options.o inode.o ioctl.o extents.o catalog.o dir.o btree.o \ -		bnode.o brec.o bfind.o tables.o unicode.o wrapper.o bitmap.o part_tbl.o +		bnode.o brec.o bfind.o tables.o unicode.o wrapper.o bitmap.o part_tbl.o \ +		attributes.o xattr.o xattr_user.o xattr_security.o xattr_trusted.o +hfsplus-$(CONFIG_HFSPLUS_FS_POSIX_ACL)	+= posix_acl.o diff --git a/fs/hfsplus/acl.h b/fs/hfsplus/acl.h new file mode 100644 index 00000000000..95c8ed9ec17 --- /dev/null +++ b/fs/hfsplus/acl.h @@ -0,0 +1,27 @@ +/* + * linux/fs/hfsplus/acl.h + * + * Vyacheslav Dubeyko <slava@dubeyko.com> + * + * Handler for Posix Access Control Lists (ACLs) support. + */ + +#include <linux/posix_acl_xattr.h> + +#ifdef CONFIG_HFSPLUS_FS_POSIX_ACL + +/* posix_acl.c */ +struct posix_acl *hfsplus_get_posix_acl(struct inode *inode, int type); +int hfsplus_set_posix_acl(struct inode *inode, struct posix_acl *acl, +		int type); +extern int hfsplus_init_posix_acl(struct inode *, struct inode *); + +#else  /* CONFIG_HFSPLUS_FS_POSIX_ACL */ +#define hfsplus_get_posix_acl NULL +#define hfsplus_set_posix_acl NULL + +static inline int hfsplus_init_posix_acl(struct inode *inode, struct inode *dir) +{ +	return 0; +} +#endif  /* CONFIG_HFSPLUS_FS_POSIX_ACL */ diff --git a/fs/hfsplus/attributes.c b/fs/hfsplus/attributes.c new file mode 100644 index 00000000000..e5b221de7de --- /dev/null +++ b/fs/hfsplus/attributes.c @@ -0,0 +1,371 @@ +/* + * 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; +} diff --git a/fs/hfsplus/bfind.c b/fs/hfsplus/bfind.c index d182438c7ae..c1422d91cd3 100644 --- a/fs/hfsplus/bfind.c +++ b/fs/hfsplus/bfind.c @@ -22,8 +22,21 @@ int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd)  		return -ENOMEM;  	fd->search_key = ptr;  	fd->key = ptr + tree->max_key_len + 2; -	dprint(DBG_BNODE_REFS, "find_init: %d (%p)\n", tree->cnid, __builtin_return_address(0)); -	mutex_lock(&tree->tree_lock); +	hfs_dbg(BNODE_REFS, "find_init: %d (%p)\n", +		tree->cnid, __builtin_return_address(0)); +	switch (tree->cnid) { +	case HFSPLUS_CAT_CNID: +		mutex_lock_nested(&tree->tree_lock, CATALOG_BTREE_MUTEX); +		break; +	case HFSPLUS_EXT_CNID: +		mutex_lock_nested(&tree->tree_lock, EXTENTS_BTREE_MUTEX); +		break; +	case HFSPLUS_ATTR_CNID: +		mutex_lock_nested(&tree->tree_lock, ATTR_BTREE_MUTEX); +		break; +	default: +		BUG(); +	}  	return 0;  } @@ -31,20 +44,83 @@ void hfs_find_exit(struct hfs_find_data *fd)  {  	hfs_bnode_put(fd->bnode);  	kfree(fd->search_key); -	dprint(DBG_BNODE_REFS, "find_exit: %d (%p)\n", fd->tree->cnid, __builtin_return_address(0)); +	hfs_dbg(BNODE_REFS, "find_exit: %d (%p)\n", +		fd->tree->cnid, __builtin_return_address(0));  	mutex_unlock(&fd->tree->tree_lock);  	fd->tree = NULL;  } -/* Find the record in bnode that best matches key (not greater than...)*/ -int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd) +int hfs_find_1st_rec_by_cnid(struct hfs_bnode *bnode, +				struct hfs_find_data *fd, +				int *begin, +				int *end, +				int *cur_rec) +{ +	__be32 cur_cnid; +	__be32 search_cnid; + +	if (bnode->tree->cnid == HFSPLUS_EXT_CNID) { +		cur_cnid = fd->key->ext.cnid; +		search_cnid = fd->search_key->ext.cnid; +	} else if (bnode->tree->cnid == HFSPLUS_CAT_CNID) { +		cur_cnid = fd->key->cat.parent; +		search_cnid = fd->search_key->cat.parent; +	} else if (bnode->tree->cnid == HFSPLUS_ATTR_CNID) { +		cur_cnid = fd->key->attr.cnid; +		search_cnid = fd->search_key->attr.cnid; +	} else { +		cur_cnid = 0;	/* used-uninitialized warning */ +		search_cnid = 0; +		BUG(); +	} + +	if (cur_cnid == search_cnid) { +		(*end) = (*cur_rec); +		if ((*begin) == (*end)) +			return 1; +	} else { +		if (be32_to_cpu(cur_cnid) < be32_to_cpu(search_cnid)) +			(*begin) = (*cur_rec) + 1; +		else +			(*end) = (*cur_rec) - 1; +	} + +	return 0; +} + +int hfs_find_rec_by_key(struct hfs_bnode *bnode, +				struct hfs_find_data *fd, +				int *begin, +				int *end, +				int *cur_rec)  {  	int cmpval; + +	cmpval = bnode->tree->keycmp(fd->key, fd->search_key); +	if (!cmpval) { +		(*end) = (*cur_rec); +		return 1; +	} +	if (cmpval < 0) +		(*begin) = (*cur_rec) + 1; +	else +		*(end) = (*cur_rec) - 1; + +	return 0; +} + +/* Find the record in bnode that best matches key (not greater than...)*/ +int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd, +					search_strategy_t rec_found) +{  	u16 off, len, keylen;  	int rec;  	int b, e;  	int res; +	if (!rec_found) +		BUG(); +  	b = 0;  	e = bnode->num_recs - 1;  	res = -ENOENT; @@ -57,17 +133,12 @@ int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd)  			goto fail;  		}  		hfs_bnode_read(bnode, fd->key, off, keylen); -		cmpval = bnode->tree->keycmp(fd->key, fd->search_key); -		if (!cmpval) { -			e = rec; +		if (rec_found(bnode, fd, &b, &e, &rec)) {  			res = 0;  			goto done;  		} -		if (cmpval < 0) -			b = rec + 1; -		else -			e = rec - 1;  	} while (b <= e); +  	if (rec != e && e >= 0) {  		len = hfs_brec_lenoff(bnode, e, &off);  		keylen = hfs_brec_keylen(bnode, e); @@ -77,19 +148,21 @@ int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd)  		}  		hfs_bnode_read(bnode, fd->key, off, keylen);  	} +  done:  	fd->record = e;  	fd->keyoffset = off;  	fd->keylength = keylen;  	fd->entryoffset = off + keylen;  	fd->entrylength = len - keylen; +  fail:  	return res;  }  /* Traverse a B*Tree from the root to a leaf finding best fit to key */  /* Return allocated copy of node found, set recnum to best record */ -int hfs_brec_find(struct hfs_find_data *fd) +int hfs_brec_find(struct hfs_find_data *fd, search_strategy_t do_key_compare)  {  	struct hfs_btree *tree;  	struct hfs_bnode *bnode; @@ -120,7 +193,7 @@ int hfs_brec_find(struct hfs_find_data *fd)  			goto invalid;  		bnode->parent = parent; -		res = __hfs_brec_find(bnode, fd); +		res = __hfs_brec_find(bnode, fd, do_key_compare);  		if (!height)  			break;  		if (fd->record < 0) @@ -135,7 +208,7 @@ int hfs_brec_find(struct hfs_find_data *fd)  	return res;  invalid: -	printk(KERN_ERR "hfs: inconsistency in B*Tree (%d,%d,%d,%u,%u)\n", +	pr_err("inconsistency in B*Tree (%d,%d,%d,%u,%u)\n",  		height, bnode->height, bnode->type, nidx, parent);  	res = -EIO;  release: @@ -147,7 +220,7 @@ int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len)  {  	int res; -	res = hfs_brec_find(fd); +	res = hfs_brec_find(fd, hfs_find_rec_by_key);  	if (res)  		return res;  	if (fd->entrylength > rec_len) diff --git a/fs/hfsplus/bitmap.c b/fs/hfsplus/bitmap.c index ad57f5991eb..d2954451519 100644 --- a/fs/hfsplus/bitmap.c +++ b/fs/hfsplus/bitmap.c @@ -15,7 +15,8 @@  #define PAGE_CACHE_BITS	(PAGE_CACHE_SIZE * 8) -int hfsplus_block_allocate(struct super_block *sb, u32 size, u32 offset, u32 *max) +int hfsplus_block_allocate(struct super_block *sb, u32 size, +		u32 offset, u32 *max)  {  	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);  	struct page *page; @@ -29,7 +30,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size, u32 offset, u32 *ma  	if (!len)  		return size; -	dprint(DBG_BITMAP, "block_allocate: %u,%u,%u\n", size, offset, len); +	hfs_dbg(BITMAP, "block_allocate: %u,%u,%u\n", size, offset, len);  	mutex_lock(&sbi->alloc_mutex);  	mapping = sbi->alloc_file->i_mapping;  	page = read_mapping_page(mapping, offset / PAGE_CACHE_BITS, NULL); @@ -88,14 +89,14 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size, u32 offset, u32 *ma  		else  			end = pptr + ((size + 31) & (PAGE_CACHE_BITS - 1)) / 32;  	} -	dprint(DBG_BITMAP, "bitmap full\n"); +	hfs_dbg(BITMAP, "bitmap full\n");  	start = size;  	goto out;  found:  	start = offset + (curr - pptr) * 32 + i;  	if (start >= size) { -		dprint(DBG_BITMAP, "bitmap full\n"); +		hfs_dbg(BITMAP, "bitmap full\n");  		goto out;  	}  	/* do any partial u32 at the start */ @@ -152,8 +153,8 @@ done:  	kunmap(page);  	*max = offset + (curr - pptr) * 32 + i - start;  	sbi->free_blocks -= *max; -	sb->s_dirt = 1; -	dprint(DBG_BITMAP, "-> %u,%u\n", start, *max); +	hfsplus_mark_mdb_dirty(sb); +	hfs_dbg(BITMAP, "-> %u,%u\n", start, *max);  out:  	mutex_unlock(&sbi->alloc_mutex);  	return start; @@ -172,15 +173,17 @@ int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count)  	if (!count)  		return 0; -	dprint(DBG_BITMAP, "block_free: %u,%u\n", offset, count); +	hfs_dbg(BITMAP, "block_free: %u,%u\n", offset, count);  	/* are all of the bits in range? */  	if ((offset + count) > sbi->total_blocks) -		return -2; +		return -ENOENT;  	mutex_lock(&sbi->alloc_mutex);  	mapping = sbi->alloc_file->i_mapping;  	pnr = offset / PAGE_CACHE_BITS;  	page = read_mapping_page(mapping, pnr, NULL); +	if (IS_ERR(page)) +		goto kaboom;  	pptr = kmap(page);  	curr = pptr + (offset & (PAGE_CACHE_BITS - 1)) / 32;  	end = pptr + PAGE_CACHE_BITS / 32; @@ -213,6 +216,8 @@ int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count)  		set_page_dirty(page);  		kunmap(page);  		page = read_mapping_page(mapping, ++pnr, NULL); +		if (IS_ERR(page)) +			goto kaboom;  		pptr = kmap(page);  		curr = pptr;  		end = pptr + PAGE_CACHE_BITS / 32; @@ -227,8 +232,14 @@ out:  	set_page_dirty(page);  	kunmap(page);  	sbi->free_blocks += len; -	sb->s_dirt = 1; +	hfsplus_mark_mdb_dirty(sb);  	mutex_unlock(&sbi->alloc_mutex);  	return 0; + +kaboom: +	pr_crit("unable to mark blocks free: error %ld\n", PTR_ERR(page)); +	mutex_unlock(&sbi->alloc_mutex); + +	return -EIO;  } diff --git a/fs/hfsplus/bnode.c b/fs/hfsplus/bnode.c index 29da6574ba7..759708fd933 100644 --- a/fs/hfsplus/bnode.c +++ b/fs/hfsplus/bnode.c @@ -27,13 +27,13 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len)  	pagep = node->page + (off >> PAGE_CACHE_SHIFT);  	off &= ~PAGE_CACHE_MASK; -	l = min(len, (int)PAGE_CACHE_SIZE - off); +	l = min_t(int, len, PAGE_CACHE_SIZE - off);  	memcpy(buf, kmap(*pagep) + off, l);  	kunmap(*pagep);  	while ((len -= l) != 0) {  		buf += l; -		l = min(len, (int)PAGE_CACHE_SIZE); +		l = min_t(int, len, PAGE_CACHE_SIZE);  		memcpy(buf, kmap(*++pagep), l);  		kunmap(*pagep);  	} @@ -42,7 +42,7 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len)  u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off)  {  	__be16 data; -	// optimize later... +	/* TODO: optimize later... */  	hfs_bnode_read(node, &data, off, 2);  	return be16_to_cpu(data);  } @@ -50,7 +50,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off)  u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off)  {  	u8 data; -	// optimize later... +	/* TODO: optimize later... */  	hfs_bnode_read(node, &data, off, 1);  	return data;  } @@ -62,7 +62,8 @@ void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off)  	tree = node->tree;  	if (node->type == HFS_NODE_LEAF || -	    tree->attributes & HFS_TREE_VARIDXKEYS) +	    tree->attributes & HFS_TREE_VARIDXKEYS || +	    node->tree->cnid == HFSPLUS_ATTR_CNID)  		key_len = hfs_bnode_read_u16(node, off) + 2;  	else  		key_len = tree->max_key_len + 2; @@ -79,14 +80,14 @@ void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len)  	pagep = node->page + (off >> PAGE_CACHE_SHIFT);  	off &= ~PAGE_CACHE_MASK; -	l = min(len, (int)PAGE_CACHE_SIZE - off); +	l = min_t(int, len, PAGE_CACHE_SIZE - off);  	memcpy(kmap(*pagep) + off, buf, l);  	set_page_dirty(*pagep);  	kunmap(*pagep);  	while ((len -= l) != 0) {  		buf += l; -		l = min(len, (int)PAGE_CACHE_SIZE); +		l = min_t(int, len, PAGE_CACHE_SIZE);  		memcpy(kmap(*++pagep), buf, l);  		set_page_dirty(*pagep);  		kunmap(*pagep); @@ -96,7 +97,7 @@ void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len)  void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data)  {  	__be16 v = cpu_to_be16(data); -	// optimize later... +	/* TODO: optimize later... */  	hfs_bnode_write(node, &v, off, 2);  } @@ -109,13 +110,13 @@ void hfs_bnode_clear(struct hfs_bnode *node, int off, int len)  	pagep = node->page + (off >> PAGE_CACHE_SHIFT);  	off &= ~PAGE_CACHE_MASK; -	l = min(len, (int)PAGE_CACHE_SIZE - off); +	l = min_t(int, len, PAGE_CACHE_SIZE - off);  	memset(kmap(*pagep) + off, 0, l);  	set_page_dirty(*pagep);  	kunmap(*pagep);  	while ((len -= l) != 0) { -		l = min(len, (int)PAGE_CACHE_SIZE); +		l = min_t(int, len, PAGE_CACHE_SIZE);  		memset(kmap(*++pagep), 0, l);  		set_page_dirty(*pagep);  		kunmap(*pagep); @@ -129,7 +130,7 @@ void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst,  	struct page **src_page, **dst_page;  	int l; -	dprint(DBG_BNODE_MOD, "copybytes: %u,%u,%u\n", dst, src, len); +	hfs_dbg(BNODE_MOD, "copybytes: %u,%u,%u\n", dst, src, len);  	if (!len)  		return;  	tree = src_node->tree; @@ -141,14 +142,14 @@ void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst,  	dst &= ~PAGE_CACHE_MASK;  	if (src == dst) { -		l = min(len, (int)PAGE_CACHE_SIZE - src); +		l = min_t(int, len, PAGE_CACHE_SIZE - src);  		memcpy(kmap(*dst_page) + src, kmap(*src_page) + src, l);  		kunmap(*src_page);  		set_page_dirty(*dst_page);  		kunmap(*dst_page);  		while ((len -= l) != 0) { -			l = min(len, (int)PAGE_CACHE_SIZE); +			l = min_t(int, len, PAGE_CACHE_SIZE);  			memcpy(kmap(*++dst_page), kmap(*++src_page), l);  			kunmap(*src_page);  			set_page_dirty(*dst_page); @@ -187,7 +188,7 @@ void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len)  	struct page **src_page, **dst_page;  	int l; -	dprint(DBG_BNODE_MOD, "movebytes: %u,%u,%u\n", dst, src, len); +	hfs_dbg(BNODE_MOD, "movebytes: %u,%u,%u\n", dst, src, len);  	if (!len)  		return;  	src += node->page_offset; @@ -212,7 +213,8 @@ void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len)  				dst_page--;  			}  			src -= len; -			memmove(kmap(*dst_page) + src, kmap(*src_page) + src, len); +			memmove(kmap(*dst_page) + src, +				kmap(*src_page) + src, len);  			kunmap(*src_page);  			set_page_dirty(*dst_page);  			kunmap(*dst_page); @@ -249,15 +251,17 @@ void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len)  		dst &= ~PAGE_CACHE_MASK;  		if (src == dst) { -			l = min(len, (int)PAGE_CACHE_SIZE - src); -			memmove(kmap(*dst_page) + src, kmap(*src_page) + src, l); +			l = min_t(int, len, PAGE_CACHE_SIZE - src); +			memmove(kmap(*dst_page) + src, +				kmap(*src_page) + src, l);  			kunmap(*src_page);  			set_page_dirty(*dst_page);  			kunmap(*dst_page);  			while ((len -= l) != 0) { -				l = min(len, (int)PAGE_CACHE_SIZE); -				memmove(kmap(*++dst_page), kmap(*++src_page), l); +				l = min_t(int, len, PAGE_CACHE_SIZE); +				memmove(kmap(*++dst_page), +					kmap(*++src_page), l);  				kunmap(*src_page);  				set_page_dirty(*dst_page);  				kunmap(*dst_page); @@ -268,7 +272,8 @@ void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len)  			do {  				src_ptr = kmap(*src_page) + src;  				dst_ptr = kmap(*dst_page) + dst; -				if (PAGE_CACHE_SIZE - src < PAGE_CACHE_SIZE - dst) { +				if (PAGE_CACHE_SIZE - src < +						PAGE_CACHE_SIZE - dst) {  					l = PAGE_CACHE_SIZE - src;  					src = 0;  					dst += l; @@ -297,34 +302,35 @@ void hfs_bnode_dump(struct hfs_bnode *node)  	__be32 cnid;  	int i, off, key_off; -	dprint(DBG_BNODE_MOD, "bnode: %d\n", node->this); +	hfs_dbg(BNODE_MOD, "bnode: %d\n", node->this);  	hfs_bnode_read(node, &desc, 0, sizeof(desc)); -	dprint(DBG_BNODE_MOD, "%d, %d, %d, %d, %d\n", +	hfs_dbg(BNODE_MOD, "%d, %d, %d, %d, %d\n",  		be32_to_cpu(desc.next), be32_to_cpu(desc.prev),  		desc.type, desc.height, be16_to_cpu(desc.num_recs));  	off = node->tree->node_size - 2;  	for (i = be16_to_cpu(desc.num_recs); i >= 0; off -= 2, i--) {  		key_off = hfs_bnode_read_u16(node, off); -		dprint(DBG_BNODE_MOD, " %d", key_off); +		hfs_dbg(BNODE_MOD, " %d", key_off);  		if (i && node->type == HFS_NODE_INDEX) {  			int tmp; -			if (node->tree->attributes & HFS_TREE_VARIDXKEYS) +			if (node->tree->attributes & HFS_TREE_VARIDXKEYS || +					node->tree->cnid == HFSPLUS_ATTR_CNID)  				tmp = hfs_bnode_read_u16(node, key_off) + 2;  			else  				tmp = node->tree->max_key_len + 2; -			dprint(DBG_BNODE_MOD, " (%d", tmp); +			hfs_dbg_cont(BNODE_MOD, " (%d", tmp);  			hfs_bnode_read(node, &cnid, key_off + tmp, 4); -			dprint(DBG_BNODE_MOD, ",%d)", be32_to_cpu(cnid)); +			hfs_dbg_cont(BNODE_MOD, ",%d)", be32_to_cpu(cnid));  		} else if (i && node->type == HFS_NODE_LEAF) {  			int tmp;  			tmp = hfs_bnode_read_u16(node, key_off); -			dprint(DBG_BNODE_MOD, " (%d)", tmp); +			hfs_dbg_cont(BNODE_MOD, " (%d)", tmp);  		}  	} -	dprint(DBG_BNODE_MOD, "\n"); +	hfs_dbg_cont(BNODE_MOD, "\n");  }  void hfs_bnode_unlink(struct hfs_bnode *node) @@ -340,7 +346,8 @@ void hfs_bnode_unlink(struct hfs_bnode *node)  			return;  		tmp->next = node->next;  		cnid = cpu_to_be32(tmp->next); -		hfs_bnode_write(tmp, &cnid, offsetof(struct hfs_bnode_desc, next), 4); +		hfs_bnode_write(tmp, &cnid, +			offsetof(struct hfs_bnode_desc, next), 4);  		hfs_bnode_put(tmp);  	} else if (node->type == HFS_NODE_LEAF)  		tree->leaf_head = node->next; @@ -351,15 +358,15 @@ void hfs_bnode_unlink(struct hfs_bnode *node)  			return;  		tmp->prev = node->prev;  		cnid = cpu_to_be32(tmp->prev); -		hfs_bnode_write(tmp, &cnid, offsetof(struct hfs_bnode_desc, prev), 4); +		hfs_bnode_write(tmp, &cnid, +			offsetof(struct hfs_bnode_desc, prev), 4);  		hfs_bnode_put(tmp);  	} else if (node->type == HFS_NODE_LEAF)  		tree->leaf_tail = node->prev; -	// move down? -	if (!node->prev && !node->next) { -		printk(KERN_DEBUG "hfs_btree_del_level\n"); -	} +	/* move down? */ +	if (!node->prev && !node->next) +		hfs_dbg(BNODE_MOD, "hfs_btree_del_level\n");  	if (!node->parent) {  		tree->root = 0;  		tree->depth = 0; @@ -379,16 +386,15 @@ struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid)  	struct hfs_bnode *node;  	if (cnid >= tree->node_count) { -		printk(KERN_ERR "hfs: request for non-existent node %d in B*Tree\n", cnid); +		pr_err("request for non-existent node %d in B*Tree\n", +		       cnid);  		return NULL;  	}  	for (node = tree->node_hash[hfs_bnode_hash(cnid)]; -	     node; node = node->next_hash) { -		if (node->this == cnid) { +			node; node = node->next_hash) +		if (node->this == cnid)  			return node; -		} -	}  	return NULL;  } @@ -402,7 +408,8 @@ static struct hfs_bnode *__hfs_bnode_create(struct hfs_btree *tree, u32 cnid)  	loff_t off;  	if (cnid >= tree->node_count) { -		printk(KERN_ERR "hfs: request for non-existent node %d in B*Tree\n", cnid); +		pr_err("request for non-existent node %d in B*Tree\n", +		       cnid);  		return NULL;  	} @@ -416,8 +423,8 @@ static struct hfs_bnode *__hfs_bnode_create(struct hfs_btree *tree, u32 cnid)  	node->this = cnid;  	set_bit(HFS_BNODE_NEW, &node->flags);  	atomic_set(&node->refcnt, 1); -	dprint(DBG_BNODE_REFS, "new_node(%d:%d): 1\n", -	       node->tree->cnid, node->this); +	hfs_dbg(BNODE_REFS, "new_node(%d:%d): 1\n", +		node->tree->cnid, node->this);  	init_waitqueue_head(&node->lock_wq);  	spin_lock(&tree->hash_lock);  	node2 = hfs_bnode_findhash(tree, cnid); @@ -429,7 +436,8 @@ static struct hfs_bnode *__hfs_bnode_create(struct hfs_btree *tree, u32 cnid)  	} else {  		spin_unlock(&tree->hash_lock);  		kfree(node); -		wait_event(node2->lock_wq, !test_bit(HFS_BNODE_NEW, &node2->flags)); +		wait_event(node2->lock_wq, +			!test_bit(HFS_BNODE_NEW, &node2->flags));  		return node2;  	}  	spin_unlock(&tree->hash_lock); @@ -460,7 +468,7 @@ void hfs_bnode_unhash(struct hfs_bnode *node)  {  	struct hfs_bnode **p; -	dprint(DBG_BNODE_REFS, "remove_node(%d:%d): %d\n", +	hfs_dbg(BNODE_REFS, "remove_node(%d:%d): %d\n",  		node->tree->cnid, node->this, atomic_read(&node->refcnt));  	for (p = &node->tree->node_hash[hfs_bnode_hash(node->this)];  	     *p && *p != node; p = &(*p)->next_hash) @@ -483,7 +491,8 @@ struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num)  	if (node) {  		hfs_bnode_get(node);  		spin_unlock(&tree->hash_lock); -		wait_event(node->lock_wq, !test_bit(HFS_BNODE_NEW, &node->flags)); +		wait_event(node->lock_wq, +			!test_bit(HFS_BNODE_NEW, &node->flags));  		if (test_bit(HFS_BNODE_ERROR, &node->flags))  			goto node_error;  		return node; @@ -497,7 +506,8 @@ struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num)  	if (!test_bit(HFS_BNODE_NEW, &node->flags))  		return node; -	desc = (struct hfs_bnode_desc *)(kmap(node->page[0]) + node->page_offset); +	desc = (struct hfs_bnode_desc *)(kmap(node->page[0]) + +			node->page_offset);  	node->prev = be32_to_cpu(desc->prev);  	node->next = be32_to_cpu(desc->next);  	node->num_recs = be16_to_cpu(desc->num_recs); @@ -556,11 +566,13 @@ node_error:  void hfs_bnode_free(struct hfs_bnode *node)  { -	//int i; +#if 0 +	int i; -	//for (i = 0; i < node->tree->pages_per_bnode; i++) -	//	if (node->page[i]) -	//		page_cache_release(node->page[i]); +	for (i = 0; i < node->tree->pages_per_bnode; i++) +		if (node->page[i]) +			page_cache_release(node->page[i]); +#endif  	kfree(node);  } @@ -574,7 +586,7 @@ struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num)  	node = hfs_bnode_findhash(tree, num);  	spin_unlock(&tree->hash_lock);  	if (node) { -		printk(KERN_CRIT "new node %u already hashed?\n", num); +		pr_crit("new node %u already hashed?\n", num);  		WARN_ON(1);  		return node;  	} @@ -588,7 +600,7 @@ struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num)  	pagep = node->page;  	memset(kmap(*pagep) + node->page_offset, 0, -	       min((int)PAGE_CACHE_SIZE, (int)tree->node_size)); +	       min_t(int, PAGE_CACHE_SIZE, tree->node_size));  	set_page_dirty(*pagep);  	kunmap(*pagep);  	for (i = 1; i < tree->pages_per_bnode; i++) { @@ -606,8 +618,9 @@ void hfs_bnode_get(struct hfs_bnode *node)  {  	if (node) {  		atomic_inc(&node->refcnt); -		dprint(DBG_BNODE_REFS, "get_node(%d:%d): %d\n", -		       node->tree->cnid, node->this, atomic_read(&node->refcnt)); +		hfs_dbg(BNODE_REFS, "get_node(%d:%d): %d\n", +			node->tree->cnid, node->this, +			atomic_read(&node->refcnt));  	}  } @@ -618,8 +631,9 @@ void hfs_bnode_put(struct hfs_bnode *node)  		struct hfs_btree *tree = node->tree;  		int i; -		dprint(DBG_BNODE_REFS, "put_node(%d:%d): %d\n", -		       node->tree->cnid, node->this, atomic_read(&node->refcnt)); +		hfs_dbg(BNODE_REFS, "put_node(%d:%d): %d\n", +			node->tree->cnid, node->this, +			atomic_read(&node->refcnt));  		BUG_ON(!atomic_read(&node->refcnt));  		if (!atomic_dec_and_lock(&node->refcnt, &tree->hash_lock))  			return; @@ -632,6 +646,8 @@ void hfs_bnode_put(struct hfs_bnode *node)  		if (test_bit(HFS_BNODE_DELETED, &node->flags)) {  			hfs_bnode_unhash(node);  			spin_unlock(&tree->hash_lock); +			if (hfs_bnode_need_zeroout(tree)) +				hfs_bnode_clear(node, 0, tree->node_size);  			hfs_bmap_free(node);  			hfs_bnode_free(node);  			return; @@ -640,3 +656,16 @@ void hfs_bnode_put(struct hfs_bnode *node)  	}  } +/* + * Unused nodes have to be zeroed if this is the catalog tree and + * a corresponding flag in the volume header is set. + */ +bool hfs_bnode_need_zeroout(struct hfs_btree *tree) +{ +	struct super_block *sb = tree->inode->i_sb; +	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); +	const u32 volume_attr = be32_to_cpu(sbi->s_vhdr->attributes); + +	return tree->cnid == HFSPLUS_CAT_CNID && +		volume_attr & HFSPLUS_VOL_UNUSED_NODE_FIX; +} diff --git a/fs/hfsplus/brec.c b/fs/hfsplus/brec.c index 2f39d05443e..6e560d56094 100644 --- a/fs/hfsplus/brec.c +++ b/fs/hfsplus/brec.c @@ -36,16 +36,22 @@ u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec)  		return 0;  	if ((node->type == HFS_NODE_INDEX) && -	   !(node->tree->attributes & HFS_TREE_VARIDXKEYS)) { +	   !(node->tree->attributes & HFS_TREE_VARIDXKEYS) && +	   (node->tree->cnid != HFSPLUS_ATTR_CNID)) {  		retval = node->tree->max_key_len + 2;  	} else { -		recoff = hfs_bnode_read_u16(node, node->tree->node_size - (rec + 1) * 2); +		recoff = hfs_bnode_read_u16(node, +			node->tree->node_size - (rec + 1) * 2);  		if (!recoff)  			return 0; +		if (recoff > node->tree->node_size - 2) { +			pr_err("recoff %d too large\n", recoff); +			return 0; +		}  		retval = hfs_bnode_read_u16(node, recoff) + 2;  		if (retval > node->tree->max_key_len + 2) { -			printk(KERN_ERR "hfs: keylen %d too large\n", +			pr_err("keylen %d too large\n",  				retval);  			retval = 0;  		} @@ -84,7 +90,8 @@ again:  	end_rec_off = tree->node_size - (node->num_recs + 1) * 2;  	end_off = hfs_bnode_read_u16(node, end_rec_off);  	end_rec_off -= 2; -	dprint(DBG_BNODE_MOD, "insert_rec: %d, %d, %d, %d\n", rec, size, end_off, end_rec_off); +	hfs_dbg(BNODE_MOD, "insert_rec: %d, %d, %d, %d\n", +		rec, size, end_off, end_rec_off);  	if (size > end_rec_off - end_off) {  		if (new_node)  			panic("not enough room!\n"); @@ -99,7 +106,9 @@ again:  	}  	node->num_recs++;  	/* write new last offset */ -	hfs_bnode_write_u16(node, offsetof(struct hfs_bnode_desc, num_recs), node->num_recs); +	hfs_bnode_write_u16(node, +		offsetof(struct hfs_bnode_desc, num_recs), +		node->num_recs);  	hfs_bnode_write_u16(node, end_rec_off, end_off + size);  	data_off = end_off;  	data_rec_off = end_rec_off + 2; @@ -143,15 +152,17 @@ skip:  		/* get index key */  		hfs_bnode_read_key(new_node, fd->search_key, 14); -		__hfs_brec_find(fd->bnode, fd); +		__hfs_brec_find(fd->bnode, fd, hfs_find_rec_by_key);  		hfs_bnode_put(new_node);  		new_node = NULL; -		if (tree->attributes & HFS_TREE_VARIDXKEYS) +		if ((tree->attributes & HFS_TREE_VARIDXKEYS) || +				(tree->cnid == HFSPLUS_ATTR_CNID))  			key_len = be16_to_cpu(fd->search_key->key_len) + 2;  		else { -			fd->search_key->key_len = cpu_to_be16(tree->max_key_len); +			fd->search_key->key_len = +				cpu_to_be16(tree->max_key_len);  			key_len = tree->max_key_len + 2;  		}  		goto again; @@ -180,7 +191,8 @@ again:  		mark_inode_dirty(tree->inode);  	}  	hfs_bnode_dump(node); -	dprint(DBG_BNODE_MOD, "remove_rec: %d, %d\n", fd->record, fd->keylength + fd->entrylength); +	hfs_dbg(BNODE_MOD, "remove_rec: %d, %d\n", +		fd->record, fd->keylength + fd->entrylength);  	if (!--node->num_recs) {  		hfs_bnode_unlink(node);  		if (!node->parent) @@ -191,10 +203,12 @@ again:  		hfs_bnode_put(node);  		node = fd->bnode = parent; -		__hfs_brec_find(node, fd); +		__hfs_brec_find(node, fd, hfs_find_rec_by_key);  		goto again;  	} -	hfs_bnode_write_u16(node, offsetof(struct hfs_bnode_desc, num_recs), node->num_recs); +	hfs_bnode_write_u16(node, +		offsetof(struct hfs_bnode_desc, num_recs), +		node->num_recs);  	if (rec_off == end_off)  		goto skip; @@ -230,7 +244,7 @@ static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)  	if (IS_ERR(new_node))  		return new_node;  	hfs_bnode_get(node); -	dprint(DBG_BNODE_MOD, "split_nodes: %d - %d - %d\n", +	hfs_dbg(BNODE_MOD, "split_nodes: %d - %d - %d\n",  		node->this, new_node->this, node->next);  	new_node->next = node->next;  	new_node->prev = node->this; @@ -355,16 +369,18 @@ again:  	parent = hfs_bnode_find(tree, node->parent);  	if (IS_ERR(parent))  		return PTR_ERR(parent); -	__hfs_brec_find(parent, fd); +	__hfs_brec_find(parent, fd, hfs_find_rec_by_key);  	hfs_bnode_dump(parent);  	rec = fd->record;  	/* size difference between old and new key */ -	if (tree->attributes & HFS_TREE_VARIDXKEYS) +	if ((tree->attributes & HFS_TREE_VARIDXKEYS) || +				(tree->cnid == HFSPLUS_ATTR_CNID))  		newkeylen = hfs_bnode_read_u16(node, 14) + 2;  	else  		fd->keylength = newkeylen = tree->max_key_len + 2; -	dprint(DBG_BNODE_MOD, "update_rec: %d, %d, %d\n", rec, fd->keylength, newkeylen); +	hfs_dbg(BNODE_MOD, "update_rec: %d, %d, %d\n", +		rec, fd->keylength, newkeylen);  	rec_off = tree->node_size - (rec + 2) * 2;  	end_rec_off = tree->node_size - (parent->num_recs + 1) * 2; @@ -375,7 +391,7 @@ again:  		end_off = hfs_bnode_read_u16(parent, end_rec_off);  		if (end_rec_off - end_off < diff) { -			printk(KERN_DEBUG "hfs: splitting index node...\n"); +			hfs_dbg(BNODE_MOD, "splitting index node\n");  			fd->bnode = parent;  			new_node = hfs_bnode_split(fd);  			if (IS_ERR(new_node)) @@ -383,7 +399,8 @@ again:  			parent = fd->bnode;  			rec = fd->record;  			rec_off = tree->node_size - (rec + 2) * 2; -			end_rec_off = tree->node_size - (parent->num_recs + 1) * 2; +			end_rec_off = tree->node_size - +				(parent->num_recs + 1) * 2;  		}  	} @@ -413,7 +430,7 @@ skip:  		hfs_bnode_read_key(new_node, fd->search_key, 14);  		cnid = cpu_to_be32(new_node->this); -		__hfs_brec_find(fd->bnode, fd); +		__hfs_brec_find(fd->bnode, fd, hfs_find_rec_by_key);  		hfs_brec_insert(fd, &cnid, sizeof(cnid));  		hfs_bnode_put(fd->bnode);  		hfs_bnode_put(new_node); @@ -481,13 +498,15 @@ static int hfs_btree_inc_height(struct hfs_btree *tree)  		/* insert old root idx into new root */  		node->parent = tree->root;  		if (node->type == HFS_NODE_LEAF || -		    tree->attributes & HFS_TREE_VARIDXKEYS) +				tree->attributes & HFS_TREE_VARIDXKEYS || +				tree->cnid == HFSPLUS_ATTR_CNID)  			key_size = hfs_bnode_read_u16(node, 14) + 2;  		else  			key_size = tree->max_key_len + 2;  		hfs_bnode_copy(new_node, 14, node, 14, key_size); -		if (!(tree->attributes & HFS_TREE_VARIDXKEYS)) { +		if (!(tree->attributes & HFS_TREE_VARIDXKEYS) && +				(tree->cnid != HFSPLUS_ATTR_CNID)) {  			key_size = tree->max_key_len + 2;  			hfs_bnode_write_u16(new_node, 14, tree->max_key_len);  		} diff --git a/fs/hfsplus/btree.c b/fs/hfsplus/btree.c index 22e4d4e3299..3345c7553ed 100644 --- a/fs/hfsplus/btree.c +++ b/fs/hfsplus/btree.c @@ -15,6 +15,118 @@  #include "hfsplus_fs.h"  #include "hfsplus_raw.h" +/* + * Initial source code of clump size calculation is gotten + * from http://opensource.apple.com/tarballs/diskdev_cmds/ + */ +#define CLUMP_ENTRIES	15 + +static short clumptbl[CLUMP_ENTRIES * 3] = { +/* + *	    Volume	Attributes	 Catalog	 Extents + *	     Size	Clump (MB)	Clump (MB)	Clump (MB) + */ +	/*   1GB */	  4,		  4,		 4, +	/*   2GB */	  6,		  6,		 4, +	/*   4GB */	  8,		  8,		 4, +	/*   8GB */	 11,		 11,		 5, +	/* +	 * For volumes 16GB and larger, we want to make sure that a full OS +	 * install won't require fragmentation of the Catalog or Attributes +	 * B-trees.  We do this by making the clump sizes sufficiently large, +	 * and by leaving a gap after the B-trees for them to grow into. +	 * +	 * For SnowLeopard 10A298, a FullNetInstall with all packages selected +	 * results in: +	 * Catalog B-tree Header +	 *	nodeSize:          8192 +	 *	totalNodes:       31616 +	 *	freeNodes:         1978 +	 * (used = 231.55 MB) +	 * Attributes B-tree Header +	 *	nodeSize:          8192 +	 *	totalNodes:       63232 +	 *	freeNodes:          958 +	 * (used = 486.52 MB) +	 * +	 * We also want Time Machine backup volumes to have a sufficiently +	 * large clump size to reduce fragmentation. +	 * +	 * The series of numbers for Catalog and Attribute form a geometric +	 * series. For Catalog (16GB to 512GB), each term is 8**(1/5) times +	 * the previous term.  For Attributes (16GB to 512GB), each term is +	 * 4**(1/5) times the previous term.  For 1TB to 16TB, each term is +	 * 2**(1/5) times the previous term. +	 */ +	/*  16GB */	 64,		 32,		 5, +	/*  32GB */	 84,		 49,		 6, +	/*  64GB */	111,		 74,		 7, +	/* 128GB */	147,		111,		 8, +	/* 256GB */	194,		169,		 9, +	/* 512GB */	256,		256,		11, +	/*   1TB */	294,		294,		14, +	/*   2TB */	338,		338,		16, +	/*   4TB */	388,		388,		20, +	/*   8TB */	446,		446,		25, +	/*  16TB */	512,		512,		32 +}; + +u32 hfsplus_calc_btree_clump_size(u32 block_size, u32 node_size, +					u64 sectors, int file_id) +{ +	u32 mod = max(node_size, block_size); +	u32 clump_size; +	int column; +	int i; + +	/* Figure out which column of the above table to use for this file. */ +	switch (file_id) { +	case HFSPLUS_ATTR_CNID: +		column = 0; +		break; +	case HFSPLUS_CAT_CNID: +		column = 1; +		break; +	default: +		column = 2; +		break; +	} + +	/* +	 * The default clump size is 0.8% of the volume size. And +	 * it must also be a multiple of the node and block size. +	 */ +	if (sectors < 0x200000) { +		clump_size = sectors << 2;	/*  0.8 %  */ +		if (clump_size < (8 * node_size)) +			clump_size = 8 * node_size; +	} else { +		/* turn exponent into table index... */ +		for (i = 0, sectors = sectors >> 22; +		     sectors && (i < CLUMP_ENTRIES - 1); +		     ++i, sectors = sectors >> 1) { +			/* empty body */ +		} + +		clump_size = clumptbl[column + (i) * 3] * 1024 * 1024; +	} + +	/* +	 * Round the clump size to a multiple of node and block size. +	 * NOTE: This rounds down. +	 */ +	clump_size /= mod; +	clump_size *= mod; + +	/* +	 * Rounding down could have rounded down to 0 if the block size was +	 * greater than the clump size.  If so, just use one block or node. +	 */ +	if (clump_size == 0) +		clump_size = mod; + +	return clump_size; +}  /* Get a reference to a B*Tree and do some initial checks */  struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id) @@ -40,8 +152,7 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id)  	tree->inode = inode;  	if (!HFSPLUS_I(tree->inode)->first_blocks) { -		printk(KERN_ERR -		       "hfs: invalid btree extent records (0 size).\n"); +		pr_err("invalid btree extent records (0 size)\n");  		goto free_inode;  	} @@ -51,7 +162,8 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id)  		goto free_inode;  	/* Load the header */ -	head = (struct hfs_btree_header_rec *)(kmap(page) + sizeof(struct hfs_bnode_desc)); +	head = (struct hfs_btree_header_rec *)(kmap(page) + +		sizeof(struct hfs_bnode_desc));  	tree->root = be32_to_cpu(head->root);  	tree->leaf_count = be32_to_cpu(head->leaf_count);  	tree->leaf_head = be32_to_cpu(head->leaf_head); @@ -67,12 +179,12 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id)  	switch (id) {  	case HFSPLUS_EXT_CNID:  		if (tree->max_key_len != HFSPLUS_EXT_KEYLEN - sizeof(u16)) { -			printk(KERN_ERR "hfs: invalid extent max_key_len %d\n", +			pr_err("invalid extent max_key_len %d\n",  				tree->max_key_len);  			goto fail_page;  		}  		if (tree->attributes & HFS_TREE_VARIDXKEYS) { -			printk(KERN_ERR "hfs: invalid extent btree flag\n"); +			pr_err("invalid extent btree flag\n");  			goto fail_page;  		} @@ -80,12 +192,12 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id)  		break;  	case HFSPLUS_CAT_CNID:  		if (tree->max_key_len != HFSPLUS_CAT_KEYLEN - sizeof(u16)) { -			printk(KERN_ERR "hfs: invalid catalog max_key_len %d\n", +			pr_err("invalid catalog max_key_len %d\n",  				tree->max_key_len);  			goto fail_page;  		}  		if (!(tree->attributes & HFS_TREE_VARIDXKEYS)) { -			printk(KERN_ERR "hfs: invalid catalog btree flag\n"); +			pr_err("invalid catalog btree flag\n");  			goto fail_page;  		} @@ -97,13 +209,21 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id)  			set_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);  		}  		break; +	case HFSPLUS_ATTR_CNID: +		if (tree->max_key_len != HFSPLUS_ATTR_KEYLEN - sizeof(u16)) { +			pr_err("invalid attributes max_key_len %d\n", +				tree->max_key_len); +			goto fail_page; +		} +		tree->keycmp = hfsplus_attr_bin_cmp_key; +		break;  	default: -		printk(KERN_ERR "hfs: unknown B*Tree requested\n"); +		pr_err("unknown B*Tree requested\n");  		goto fail_page;  	}  	if (!(tree->attributes & HFS_TREE_BIGKEYS)) { -		printk(KERN_ERR "hfs: invalid btree flag\n"); +		pr_err("invalid btree flag\n");  		goto fail_page;  	} @@ -115,7 +235,9 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id)  	tree->node_size_shift = ffs(size) - 1; -	tree->pages_per_bnode = (tree->node_size + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT; +	tree->pages_per_bnode = +		(tree->node_size + PAGE_CACHE_SIZE - 1) >> +		PAGE_CACHE_SHIFT;  	kunmap(page);  	page_cache_release(page); @@ -144,8 +266,10 @@ void hfs_btree_close(struct hfs_btree *tree)  		while ((node = tree->node_hash[i])) {  			tree->node_hash[i] = node->next_hash;  			if (atomic_read(&node->refcnt)) -				printk(KERN_CRIT "hfs: node %d:%d still has %d user(s)!\n", -					node->tree->cnid, node->this, atomic_read(&node->refcnt)); +				pr_crit("node %d:%d " +						"still has %d user(s)!\n", +					node->tree->cnid, node->this, +					atomic_read(&node->refcnt));  			hfs_bnode_free(node);  			tree->node_hash_cnt--;  		} @@ -154,7 +278,7 @@ void hfs_btree_close(struct hfs_btree *tree)  	kfree(tree);  } -void hfs_btree_write(struct hfs_btree *tree) +int hfs_btree_write(struct hfs_btree *tree)  {  	struct hfs_btree_header_rec *head;  	struct hfs_bnode *node; @@ -163,10 +287,11 @@ void hfs_btree_write(struct hfs_btree *tree)  	node = hfs_bnode_find(tree, 0);  	if (IS_ERR(node))  		/* panic? */ -		return; +		return -EIO;  	/* Load the header */  	page = node->page[0]; -	head = (struct hfs_btree_header_rec *)(kmap(page) + sizeof(struct hfs_bnode_desc)); +	head = (struct hfs_btree_header_rec *)(kmap(page) + +		sizeof(struct hfs_bnode_desc));  	head->root = cpu_to_be32(tree->root);  	head->leaf_count = cpu_to_be32(tree->leaf_count); @@ -180,6 +305,7 @@ void hfs_btree_write(struct hfs_btree *tree)  	kunmap(page);  	set_page_dirty(page);  	hfs_bnode_put(node); +	return 0;  }  static struct hfs_bnode *hfs_bmap_new_bmap(struct hfs_bnode *prev, u32 idx) @@ -232,7 +358,7 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)  		u32 count;  		int res; -		res = hfsplus_file_extend(inode); +		res = hfsplus_file_extend(inode, hfs_bnode_need_zeroout(tree));  		if (res)  			return ERR_PTR(res);  		hip->phys_size = inode->i_size = @@ -272,7 +398,8 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)  						tree->free_nodes--;  						mark_inode_dirty(tree->inode);  						hfs_bnode_put(node); -						return hfs_bnode_create(tree, idx); +						return hfs_bnode_create(tree, +							idx);  					}  				}  			} @@ -287,7 +414,7 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)  		kunmap(*pagep);  		nidx = node->next;  		if (!nidx) { -			printk(KERN_DEBUG "hfs: create new bmap node...\n"); +			hfs_dbg(BNODE_MOD, "create new bmap node\n");  			next_node = hfs_bmap_new_bmap(node, idx);  		} else  			next_node = hfs_bnode_find(tree, nidx); @@ -313,7 +440,7 @@ void hfs_bmap_free(struct hfs_bnode *node)  	u32 nidx;  	u8 *data, byte, m; -	dprint(DBG_BNODE_MOD, "btree_free_node: %u\n", node->this); +	hfs_dbg(BNODE_MOD, "btree_free_node: %u\n", node->this);  	BUG_ON(!node->this);  	tree = node->tree;  	nidx = node->this; @@ -329,7 +456,9 @@ void hfs_bmap_free(struct hfs_bnode *node)  		hfs_bnode_put(node);  		if (!i) {  			/* panic */; -			printk(KERN_CRIT "hfs: unable to free bnode %u. bmap not found!\n", node->this); +			pr_crit("unable to free bnode %u. " +					"bmap not found!\n", +				node->this);  			return;  		}  		node = hfs_bnode_find(tree, i); @@ -337,7 +466,9 @@ void hfs_bmap_free(struct hfs_bnode *node)  			return;  		if (node->type != HFS_NODE_MAP) {  			/* panic */; -			printk(KERN_CRIT "hfs: invalid bmap found! (%u,%d)\n", node->this, node->type); +			pr_crit("invalid bmap found! " +					"(%u,%d)\n", +				node->this, node->type);  			hfs_bnode_put(node);  			return;  		} @@ -350,7 +481,9 @@ void hfs_bmap_free(struct hfs_bnode *node)  	m = 1 << (~nidx & 7);  	byte = data[off];  	if (!(byte & m)) { -		printk(KERN_CRIT "hfs: trying to free free bnode %u(%d)\n", node->this, node->type); +		pr_crit("trying to free free bnode " +				"%u(%d)\n", +			node->this, node->type);  		kunmap(page);  		hfs_bnode_put(node);  		return; diff --git a/fs/hfsplus/catalog.c b/fs/hfsplus/catalog.c index 8af45fc5b05..32602c667b4 100644 --- a/fs/hfsplus/catalog.c +++ b/fs/hfsplus/catalog.c @@ -45,7 +45,8 @@ void hfsplus_cat_build_key(struct super_block *sb, hfsplus_btree_key *key,  	key->cat.parent = cpu_to_be32(parent);  	if (str) { -		hfsplus_asc2uni(sb, &key->cat.name, str->name, str->len); +		hfsplus_asc2uni(sb, &key->cat.name, HFSPLUS_MAX_STRLEN, +					str->name, str->len);  		len = be16_to_cpu(key->cat.name.length);  	} else {  		key->cat.name.length = 0; @@ -80,8 +81,8 @@ void hfsplus_cat_set_perms(struct inode *inode, struct hfsplus_perm *perms)  	perms->userflags = HFSPLUS_I(inode)->userflags;  	perms->mode = cpu_to_be16(inode->i_mode); -	perms->owner = cpu_to_be32(inode->i_uid); -	perms->group = cpu_to_be32(inode->i_gid); +	perms->owner = cpu_to_be32(i_uid_read(inode)); +	perms->group = cpu_to_be32(i_gid_read(inode));  	if (S_ISREG(inode->i_mode))  		perms->dev = cpu_to_be32(inode->i_nlink); @@ -91,7 +92,8 @@ void hfsplus_cat_set_perms(struct inode *inode, struct hfsplus_perm *perms)  		perms->dev = 0;  } -static int hfsplus_cat_build_record(hfsplus_cat_entry *entry, u32 cnid, struct inode *inode) +static int hfsplus_cat_build_record(hfsplus_cat_entry *entry, +		u32 cnid, struct inode *inode)  {  	struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb); @@ -101,6 +103,8 @@ static int hfsplus_cat_build_record(hfsplus_cat_entry *entry, u32 cnid, struct i  		folder = &entry->folder;  		memset(folder, 0, sizeof(*folder));  		folder->type = cpu_to_be16(HFSPLUS_FOLDER); +		if (test_bit(HFSPLUS_SB_HFSX, &sbi->flags)) +			folder->flags |= cpu_to_be16(HFSPLUS_HAS_FOLDER_COUNT);  		folder->id = cpu_to_be32(inode->i_ino);  		HFSPLUS_I(inode)->create_date =  			folder->create_date = @@ -128,20 +132,32 @@ static int hfsplus_cat_build_record(hfsplus_cat_entry *entry, u32 cnid, struct i  		if (cnid == inode->i_ino) {  			hfsplus_cat_set_perms(inode, &file->permissions);  			if (S_ISLNK(inode->i_mode)) { -				file->user_info.fdType = cpu_to_be32(HFSP_SYMLINK_TYPE); -				file->user_info.fdCreator = cpu_to_be32(HFSP_SYMLINK_CREATOR); +				file->user_info.fdType = +					cpu_to_be32(HFSP_SYMLINK_TYPE); +				file->user_info.fdCreator = +					cpu_to_be32(HFSP_SYMLINK_CREATOR);  			} else { -				file->user_info.fdType = cpu_to_be32(sbi->type); -				file->user_info.fdCreator = cpu_to_be32(sbi->creator); +				file->user_info.fdType = +					cpu_to_be32(sbi->type); +				file->user_info.fdCreator = +					cpu_to_be32(sbi->creator);  			} -			if ((file->permissions.rootflags | file->permissions.userflags) & HFSPLUS_FLG_IMMUTABLE) -				file->flags |= cpu_to_be16(HFSPLUS_FILE_LOCKED); +			if (HFSPLUS_FLG_IMMUTABLE & +					(file->permissions.rootflags | +					file->permissions.userflags)) +				file->flags |= +					cpu_to_be16(HFSPLUS_FILE_LOCKED);  		} else { -			file->user_info.fdType = cpu_to_be32(HFSP_HARDLINK_TYPE); -			file->user_info.fdCreator = cpu_to_be32(HFSP_HFSPLUS_CREATOR); -			file->user_info.fdFlags = cpu_to_be16(0x100); -			file->create_date = HFSPLUS_I(sbi->hidden_dir)->create_date; -			file->permissions.dev = cpu_to_be32(HFSPLUS_I(inode)->linkid); +			file->user_info.fdType = +				cpu_to_be32(HFSP_HARDLINK_TYPE); +			file->user_info.fdCreator = +				cpu_to_be32(HFSP_HFSPLUS_CREATOR); +			file->user_info.fdFlags = +				cpu_to_be16(0x100); +			file->create_date = +				HFSPLUS_I(sbi->hidden_dir)->create_date; +			file->permissions.dev = +				cpu_to_be32(HFSPLUS_I(inode)->linkid);  		}  		return sizeof(*file);  	} @@ -154,7 +170,8 @@ static int hfsplus_fill_cat_thread(struct super_block *sb,  	entry->type = cpu_to_be16(type);  	entry->thread.reserved = 0;  	entry->thread.parentID = cpu_to_be32(parentid); -	hfsplus_asc2uni(sb, &entry->thread.nodeName, str->name, str->len); +	hfsplus_asc2uni(sb, &entry->thread.nodeName, HFSPLUS_MAX_STRLEN, +				str->name, str->len);  	return 10 + be16_to_cpu(entry->thread.nodeName.length) * 2;  } @@ -173,21 +190,53 @@ int hfsplus_find_cat(struct super_block *sb, u32 cnid,  	type = be16_to_cpu(tmp.type);  	if (type != HFSPLUS_FOLDER_THREAD && type != HFSPLUS_FILE_THREAD) { -		printk(KERN_ERR "hfs: found bad thread record in catalog\n"); +		pr_err("found bad thread record in catalog\n");  		return -EIO;  	}  	if (be16_to_cpu(tmp.thread.nodeName.length) > 255) { -		printk(KERN_ERR "hfs: catalog name length corrupted\n"); +		pr_err("catalog name length corrupted\n");  		return -EIO;  	} -	hfsplus_cat_build_key_uni(fd->search_key, be32_to_cpu(tmp.thread.parentID), -				 &tmp.thread.nodeName); -	return hfs_brec_find(fd); +	hfsplus_cat_build_key_uni(fd->search_key, +		be32_to_cpu(tmp.thread.parentID), +		&tmp.thread.nodeName); +	return hfs_brec_find(fd, hfs_find_rec_by_key); +} + +static void hfsplus_subfolders_inc(struct inode *dir) +{ +	struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb); + +	if (test_bit(HFSPLUS_SB_HFSX, &sbi->flags)) { +		/* +		 * Increment subfolder count. Note, the value is only meaningful +		 * for folders with HFSPLUS_HAS_FOLDER_COUNT flag set. +		 */ +		HFSPLUS_I(dir)->subfolders++; +	} +} + +static void hfsplus_subfolders_dec(struct inode *dir) +{ +	struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb); + +	if (test_bit(HFSPLUS_SB_HFSX, &sbi->flags)) { +		/* +		 * Decrement subfolder count. Note, the value is only meaningful +		 * for folders with HFSPLUS_HAS_FOLDER_COUNT flag set. +		 * +		 * Check for zero. Some subfolders may have been created +		 * by an implementation ignorant of this counter. +		 */ +		if (HFSPLUS_I(dir)->subfolders) +			HFSPLUS_I(dir)->subfolders--; +	}  } -int hfsplus_create_cat(u32 cnid, struct inode *dir, struct qstr *str, struct inode *inode) +int hfsplus_create_cat(u32 cnid, struct inode *dir, +		struct qstr *str, struct inode *inode)  {  	struct super_block *sb = dir->i_sb;  	struct hfs_find_data fd; @@ -195,14 +244,18 @@ int hfsplus_create_cat(u32 cnid, struct inode *dir, struct qstr *str, struct ino  	int entry_size;  	int err; -	dprint(DBG_CAT_MOD, "create_cat: %s,%u(%d)\n", str->name, cnid, inode->i_nlink); -	hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); +	hfs_dbg(CAT_MOD, "create_cat: %s,%u(%d)\n", +		str->name, cnid, inode->i_nlink); +	err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); +	if (err) +		return err;  	hfsplus_cat_build_key(sb, fd.search_key, cnid, NULL); -	entry_size = hfsplus_fill_cat_thread(sb, &entry, S_ISDIR(inode->i_mode) ? +	entry_size = hfsplus_fill_cat_thread(sb, &entry, +		S_ISDIR(inode->i_mode) ?  			HFSPLUS_FOLDER_THREAD : HFSPLUS_FILE_THREAD, -			dir->i_ino, str); -	err = hfs_brec_find(&fd); +		dir->i_ino, str); +	err = hfs_brec_find(&fd, hfs_find_rec_by_key);  	if (err != -ENOENT) {  		if (!err)  			err = -EEXIST; @@ -214,7 +267,7 @@ int hfsplus_create_cat(u32 cnid, struct inode *dir, struct qstr *str, struct ino  	hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, str);  	entry_size = hfsplus_cat_build_record(&entry, cnid, inode); -	err = hfs_brec_find(&fd); +	err = hfs_brec_find(&fd, hfs_find_rec_by_key);  	if (err != -ENOENT) {  		/* panic? */  		if (!err) @@ -226,14 +279,17 @@ int hfsplus_create_cat(u32 cnid, struct inode *dir, struct qstr *str, struct ino  		goto err1;  	dir->i_size++; +	if (S_ISDIR(inode->i_mode)) +		hfsplus_subfolders_inc(dir);  	dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC; -	mark_inode_dirty(dir); +	hfsplus_mark_inode_dirty(dir, HFSPLUS_I_CAT_DIRTY); +  	hfs_find_exit(&fd);  	return 0;  err1:  	hfsplus_cat_build_key(sb, fd.search_key, cnid, NULL); -	if (!hfs_brec_find(&fd)) +	if (!hfs_brec_find(&fd, hfs_find_rec_by_key))  		hfs_brec_remove(&fd);  err2:  	hfs_find_exit(&fd); @@ -249,27 +305,33 @@ int hfsplus_delete_cat(u32 cnid, struct inode *dir, struct qstr *str)  	int err, off;  	u16 type; -	dprint(DBG_CAT_MOD, "delete_cat: %s,%u\n", str ? str->name : NULL, cnid); -	hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); +	hfs_dbg(CAT_MOD, "delete_cat: %s,%u\n", str ? str->name : NULL, cnid); +	err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); +	if (err) +		return err;  	if (!str) {  		int len;  		hfsplus_cat_build_key(sb, fd.search_key, cnid, NULL); -		err = hfs_brec_find(&fd); +		err = hfs_brec_find(&fd, hfs_find_rec_by_key);  		if (err)  			goto out; -		off = fd.entryoffset + offsetof(struct hfsplus_cat_thread, nodeName); +		off = fd.entryoffset + +			offsetof(struct hfsplus_cat_thread, nodeName);  		fd.search_key->cat.parent = cpu_to_be32(dir->i_ino); -		hfs_bnode_read(fd.bnode, &fd.search_key->cat.name.length, off, 2); +		hfs_bnode_read(fd.bnode, +			&fd.search_key->cat.name.length, off, 2);  		len = be16_to_cpu(fd.search_key->cat.name.length) * 2; -		hfs_bnode_read(fd.bnode, &fd.search_key->cat.name.unicode, off + 2, len); +		hfs_bnode_read(fd.bnode, +			&fd.search_key->cat.name.unicode, +			off + 2, len);  		fd.search_key->key_len = cpu_to_be16(6 + len);  	} else  		hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, str); -	err = hfs_brec_find(&fd); +	err = hfs_brec_find(&fd, hfs_find_rec_by_key);  	if (err)  		goto out; @@ -281,7 +343,8 @@ int hfsplus_delete_cat(u32 cnid, struct inode *dir, struct qstr *str)  		hfsplus_free_fork(sb, cnid, &fork, HFSPLUS_TYPE_DATA);  #endif -		off = fd.entryoffset + offsetof(struct hfsplus_cat_file, rsrc_fork); +		off = fd.entryoffset + +			offsetof(struct hfsplus_cat_file, rsrc_fork);  		hfs_bnode_read(fd.bnode, &fork, off, sizeof(fork));  		hfsplus_free_fork(sb, cnid, &fork, HFSPLUS_TYPE_RSRC);  	} @@ -298,7 +361,7 @@ int hfsplus_delete_cat(u32 cnid, struct inode *dir, struct qstr *str)  		goto out;  	hfsplus_cat_build_key(sb, fd.search_key, cnid, NULL); -	err = hfs_brec_find(&fd); +	err = hfs_brec_find(&fd, hfs_find_rec_by_key);  	if (err)  		goto out; @@ -307,8 +370,16 @@ int hfsplus_delete_cat(u32 cnid, struct inode *dir, struct qstr *str)  		goto out;  	dir->i_size--; +	if (type == HFSPLUS_FOLDER) +		hfsplus_subfolders_dec(dir);  	dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC; -	mark_inode_dirty(dir); +	hfsplus_mark_inode_dirty(dir, HFSPLUS_I_CAT_DIRTY); + +	if (type == HFSPLUS_FILE || type == HFSPLUS_FOLDER) { +		if (HFSPLUS_SB(sb)->attr_tree) +			hfsplus_delete_all_attrs(dir, cnid); +	} +  out:  	hfs_find_exit(&fd); @@ -323,25 +394,33 @@ int hfsplus_rename_cat(u32 cnid,  	struct hfs_find_data src_fd, dst_fd;  	hfsplus_cat_entry entry;  	int entry_size, type; -	int err = 0; +	int err; -	dprint(DBG_CAT_MOD, "rename_cat: %u - %lu,%s - %lu,%s\n", cnid, src_dir->i_ino, src_name->name, +	hfs_dbg(CAT_MOD, "rename_cat: %u - %lu,%s - %lu,%s\n", +		cnid, src_dir->i_ino, src_name->name,  		dst_dir->i_ino, dst_name->name); -	hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &src_fd); +	err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &src_fd); +	if (err) +		return err;  	dst_fd = src_fd;  	/* find the old dir entry and read the data */  	hfsplus_cat_build_key(sb, src_fd.search_key, src_dir->i_ino, src_name); -	err = hfs_brec_find(&src_fd); +	err = hfs_brec_find(&src_fd, hfs_find_rec_by_key);  	if (err)  		goto out; +	if (src_fd.entrylength > sizeof(entry) || src_fd.entrylength < 0) { +		err = -EIO; +		goto out; +	}  	hfs_bnode_read(src_fd.bnode, &entry, src_fd.entryoffset,  				src_fd.entrylength); +	type = be16_to_cpu(entry.type);  	/* create new dir entry with the data from the old entry */  	hfsplus_cat_build_key(sb, dst_fd.search_key, dst_dir->i_ino, dst_name); -	err = hfs_brec_find(&dst_fd); +	err = hfs_brec_find(&dst_fd, hfs_find_rec_by_key);  	if (err != -ENOENT) {  		if (!err)  			err = -EEXIST; @@ -352,24 +431,26 @@ int hfsplus_rename_cat(u32 cnid,  	if (err)  		goto out;  	dst_dir->i_size++; +	if (type == HFSPLUS_FOLDER) +		hfsplus_subfolders_inc(dst_dir);  	dst_dir->i_mtime = dst_dir->i_ctime = CURRENT_TIME_SEC; -	mark_inode_dirty(dst_dir);  	/* finally remove the old entry */  	hfsplus_cat_build_key(sb, src_fd.search_key, src_dir->i_ino, src_name); -	err = hfs_brec_find(&src_fd); +	err = hfs_brec_find(&src_fd, hfs_find_rec_by_key);  	if (err)  		goto out;  	err = hfs_brec_remove(&src_fd);  	if (err)  		goto out;  	src_dir->i_size--; +	if (type == HFSPLUS_FOLDER) +		hfsplus_subfolders_dec(src_dir);  	src_dir->i_mtime = src_dir->i_ctime = CURRENT_TIME_SEC; -	mark_inode_dirty(src_dir);  	/* remove old thread entry */  	hfsplus_cat_build_key(sb, src_fd.search_key, cnid, NULL); -	err = hfs_brec_find(&src_fd); +	err = hfs_brec_find(&src_fd, hfs_find_rec_by_key);  	if (err)  		goto out;  	type = hfs_bnode_read_u16(src_fd.bnode, src_fd.entryoffset); @@ -379,14 +460,18 @@ int hfsplus_rename_cat(u32 cnid,  	/* create new thread entry */  	hfsplus_cat_build_key(sb, dst_fd.search_key, cnid, NULL); -	entry_size = hfsplus_fill_cat_thread(sb, &entry, type, dst_dir->i_ino, dst_name); -	err = hfs_brec_find(&dst_fd); +	entry_size = hfsplus_fill_cat_thread(sb, &entry, type, +		dst_dir->i_ino, dst_name); +	err = hfs_brec_find(&dst_fd, hfs_find_rec_by_key);  	if (err != -ENOENT) {  		if (!err)  			err = -EEXIST;  		goto out;  	}  	err = hfs_brec_insert(&dst_fd, &entry, entry_size); + +	hfsplus_mark_inode_dirty(dst_dir, HFSPLUS_I_CAT_DIRTY); +	hfsplus_mark_inode_dirty(src_dir, HFSPLUS_I_CAT_DIRTY);  out:  	hfs_bnode_put(dst_fd.bnode);  	hfs_find_exit(&src_fd); diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index 9d59c0571f5..610a3260bef 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -12,9 +12,12 @@  #include <linux/fs.h>  #include <linux/slab.h>  #include <linux/random.h> +#include <linux/nls.h>  #include "hfsplus_fs.h"  #include "hfsplus_raw.h" +#include "xattr.h" +#include "acl.h"  static inline void hfsplus_instantiate(struct dentry *dentry,  				       struct inode *inode, u32 cnid) @@ -25,7 +28,7 @@ static inline void hfsplus_instantiate(struct dentry *dentry,  /* Find the entry inside dir named dentry->d_name */  static struct dentry *hfsplus_lookup(struct inode *dir, struct dentry *dentry, -				     struct nameidata *nd) +				     unsigned int flags)  {  	struct inode *inode = NULL;  	struct hfs_find_data fd; @@ -37,9 +40,10 @@ static struct dentry *hfsplus_lookup(struct inode *dir, struct dentry *dentry,  	sb = dir->i_sb; -	dentry->d_op = &hfsplus_dentry_operations;  	dentry->d_fsdata = NULL; -	hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); +	err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); +	if (err) +		return ERR_PTR(err);  	hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, &dentry->d_name);  again:  	err = hfs_brec_read(&fd, &entry, sizeof(entry)); @@ -66,11 +70,17 @@ again:  			goto fail;  		}  		cnid = be32_to_cpu(entry.file.id); -		if (entry.file.user_info.fdType == cpu_to_be32(HFSP_HARDLINK_TYPE) && -		    entry.file.user_info.fdCreator == cpu_to_be32(HFSP_HFSPLUS_CREATOR) && -		    (entry.file.create_date == HFSPLUS_I(HFSPLUS_SB(sb)->hidden_dir)->create_date || -		     entry.file.create_date == HFSPLUS_I(sb->s_root->d_inode)->create_date) && -		    HFSPLUS_SB(sb)->hidden_dir) { +		if (entry.file.user_info.fdType == +				cpu_to_be32(HFSP_HARDLINK_TYPE) && +				entry.file.user_info.fdCreator == +				cpu_to_be32(HFSP_HFSPLUS_CREATOR) && +				(entry.file.create_date == +					HFSPLUS_I(HFSPLUS_SB(sb)->hidden_dir)-> +						create_date || +				entry.file.create_date == +					HFSPLUS_I(sb->s_root->d_inode)-> +						create_date) && +				HFSPLUS_SB(sb)->hidden_dir) {  			struct qstr str;  			char name[32]; @@ -83,17 +93,19 @@ again:  				linkid = 0;  			} else {  				dentry->d_fsdata = (void *)(unsigned long)cnid; -				linkid = be32_to_cpu(entry.file.permissions.dev); +				linkid = +					be32_to_cpu(entry.file.permissions.dev);  				str.len = sprintf(name, "iNode%d", linkid);  				str.name = name;  				hfsplus_cat_build_key(sb, fd.search_key, -					HFSPLUS_SB(sb)->hidden_dir->i_ino, &str); +					HFSPLUS_SB(sb)->hidden_dir->i_ino, +					&str);  				goto again;  			}  		} else if (!dentry->d_fsdata)  			dentry->d_fsdata = (void *)(unsigned long)cnid;  	} else { -		printk(KERN_ERR "hfs: invalid catalog entry type in lookup\n"); +		pr_err("invalid catalog entry type in lookup\n");  		err = -EIO;  		goto fail;  	} @@ -111,73 +123,90 @@ fail:  	return ERR_PTR(err);  } -static int hfsplus_readdir(struct file *filp, void *dirent, filldir_t filldir) +static int hfsplus_readdir(struct file *file, struct dir_context *ctx)  { -	struct inode *inode = filp->f_path.dentry->d_inode; +	struct inode *inode = file_inode(file);  	struct super_block *sb = inode->i_sb;  	int len, err; -	char strbuf[HFSPLUS_MAX_STRLEN + 1]; +	char *strbuf;  	hfsplus_cat_entry entry;  	struct hfs_find_data fd;  	struct hfsplus_readdir_data *rd;  	u16 type; -	if (filp->f_pos >= inode->i_size) +	if (file->f_pos >= inode->i_size)  		return 0; -	hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); +	err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); +	if (err) +		return err; +	strbuf = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_MAX_STRLEN + 1, GFP_KERNEL); +	if (!strbuf) { +		err = -ENOMEM; +		goto out; +	}  	hfsplus_cat_build_key(sb, fd.search_key, inode->i_ino, NULL); -	err = hfs_brec_find(&fd); +	err = hfs_brec_find(&fd, hfs_find_rec_by_key);  	if (err)  		goto out; -	switch ((u32)filp->f_pos) { -	case 0: +	if (ctx->pos == 0) {  		/* This is completely artificial... */ -		if (filldir(dirent, ".", 1, 0, inode->i_ino, DT_DIR)) +		if (!dir_emit_dot(file, ctx))  			goto out; -		filp->f_pos++; -		/* fall through */ -	case 1: -		hfs_bnode_read(fd.bnode, &entry, fd.entryoffset, fd.entrylength); +		ctx->pos = 1; +	} +	if (ctx->pos == 1) { +		if (fd.entrylength > sizeof(entry) || fd.entrylength < 0) { +			err = -EIO; +			goto out; +		} + +		hfs_bnode_read(fd.bnode, &entry, fd.entryoffset, +			fd.entrylength);  		if (be16_to_cpu(entry.type) != HFSPLUS_FOLDER_THREAD) { -			printk(KERN_ERR "hfs: bad catalog folder thread\n"); +			pr_err("bad catalog folder thread\n");  			err = -EIO;  			goto out;  		}  		if (fd.entrylength < HFSPLUS_MIN_THREAD_SZ) { -			printk(KERN_ERR "hfs: truncated catalog thread\n"); +			pr_err("truncated catalog thread\n");  			err = -EIO;  			goto out;  		} -		if (filldir(dirent, "..", 2, 1, +		if (!dir_emit(ctx, "..", 2,  			    be32_to_cpu(entry.thread.parentID), DT_DIR))  			goto out; -		filp->f_pos++; -		/* fall through */ -	default: -		if (filp->f_pos >= inode->i_size) -			goto out; -		err = hfs_brec_goto(&fd, filp->f_pos - 1); -		if (err) -			goto out; +		ctx->pos = 2;  	} - +	if (ctx->pos >= inode->i_size) +		goto out; +	err = hfs_brec_goto(&fd, ctx->pos - 1); +	if (err) +		goto out;  	for (;;) {  		if (be32_to_cpu(fd.key->cat.parent) != inode->i_ino) { -			printk(KERN_ERR "hfs: walked past end of dir\n"); +			pr_err("walked past end of dir\n"); +			err = -EIO; +			goto out; +		} + +		if (fd.entrylength > sizeof(entry) || fd.entrylength < 0) {  			err = -EIO;  			goto out;  		} -		hfs_bnode_read(fd.bnode, &entry, fd.entryoffset, fd.entrylength); + +		hfs_bnode_read(fd.bnode, &entry, fd.entryoffset, +			fd.entrylength);  		type = be16_to_cpu(entry.type); -		len = HFSPLUS_MAX_STRLEN; +		len = NLS_MAX_CHARSET_SIZE * HFSPLUS_MAX_STRLEN;  		err = hfsplus_uni2asc(sb, &fd.key->cat.name, strbuf, &len);  		if (err)  			goto out;  		if (type == HFSPLUS_FOLDER) { -			if (fd.entrylength < sizeof(struct hfsplus_cat_folder)) { -				printk(KERN_ERR "hfs: small dir entry\n"); +			if (fd.entrylength < +					sizeof(struct hfsplus_cat_folder)) { +				pr_err("small dir entry\n");  				err = -EIO;  				goto out;  			} @@ -185,44 +214,63 @@ static int hfsplus_readdir(struct file *filp, void *dirent, filldir_t filldir)  			    HFSPLUS_SB(sb)->hidden_dir->i_ino ==  					be32_to_cpu(entry.folder.id))  				goto next; -			if (filldir(dirent, strbuf, len, filp->f_pos, +			if (!dir_emit(ctx, strbuf, len,  				    be32_to_cpu(entry.folder.id), DT_DIR))  				break;  		} else if (type == HFSPLUS_FILE) { +			u16 mode; +			unsigned type = DT_UNKNOWN; +  			if (fd.entrylength < sizeof(struct hfsplus_cat_file)) { -				printk(KERN_ERR "hfs: small file entry\n"); +				pr_err("small file entry\n");  				err = -EIO;  				goto out;  			} -			if (filldir(dirent, strbuf, len, filp->f_pos, -				    be32_to_cpu(entry.file.id), DT_REG)) + +			mode = be16_to_cpu(entry.file.permissions.mode); +			if (S_ISREG(mode)) +				type = DT_REG; +			else if (S_ISLNK(mode)) +				type = DT_LNK; +			else if (S_ISFIFO(mode)) +				type = DT_FIFO; +			else if (S_ISCHR(mode)) +				type = DT_CHR; +			else if (S_ISBLK(mode)) +				type = DT_BLK; +			else if (S_ISSOCK(mode)) +				type = DT_SOCK; + +			if (!dir_emit(ctx, strbuf, len, +				      be32_to_cpu(entry.file.id), type))  				break;  		} else { -			printk(KERN_ERR "hfs: bad catalog entry type\n"); +			pr_err("bad catalog entry type\n");  			err = -EIO;  			goto out;  		} -	next: -		filp->f_pos++; -		if (filp->f_pos >= inode->i_size) +next: +		ctx->pos++; +		if (ctx->pos >= inode->i_size)  			goto out;  		err = hfs_brec_goto(&fd, 1);  		if (err)  			goto out;  	} -	rd = filp->private_data; +	rd = file->private_data;  	if (!rd) {  		rd = kmalloc(sizeof(struct hfsplus_readdir_data), GFP_KERNEL);  		if (!rd) {  			err = -ENOMEM;  			goto out;  		} -		filp->private_data = rd; -		rd->file = filp; +		file->private_data = rd; +		rd->file = file;  		list_add(&rd->list, &HFSPLUS_I(inode)->open_dir_list);  	}  	memcpy(&rd->key, fd.key, sizeof(struct hfsplus_cat_key));  out: +	kfree(strbuf);  	hfs_find_exit(&fd);  	return err;  } @@ -273,7 +321,8 @@ static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,  		HFSPLUS_I(inode)->linkid = id;  		cnid = sbi->next_cnid++;  		src_dentry->d_fsdata = (void *)(unsigned long)cnid; -		res = hfsplus_create_cat(cnid, src_dir, &src_dentry->d_name, inode); +		res = hfsplus_create_cat(cnid, src_dir, +			&src_dentry->d_name, inode);  		if (res)  			/* panic? */  			goto out; @@ -290,7 +339,7 @@ static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,  	inode->i_ctime = CURRENT_TIME_SEC;  	mark_inode_dirty(inode);  	sbi->file_count++; -	dst_dir->i_sb->s_dirt = 1; +	hfsplus_mark_mdb_dirty(dst_dir->i_sb);  out:  	mutex_unlock(&sbi->vh_mutex);  	return res; @@ -395,12 +444,21 @@ static int hfsplus_symlink(struct inode *dir, struct dentry *dentry,  	if (res)  		goto out_err; +	res = hfsplus_init_inode_security(inode, dir, &dentry->d_name); +	if (res == -EOPNOTSUPP) +		res = 0; /* Operation is not supported. */ +	else if (res) { +		/* Try to delete anyway without error analysis. */ +		hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name); +		goto out_err; +	} +  	hfsplus_instantiate(dentry, inode, inode->i_ino);  	mark_inode_dirty(inode);  	goto out;  out_err: -	inode->i_nlink = 0; +	clear_nlink(inode);  	hfsplus_delete_inode(inode);  	iput(inode);  out: @@ -409,7 +467,7 @@ out:  }  static int hfsplus_mknod(struct inode *dir, struct dentry *dentry, -			 int mode, dev_t rdev) +			 umode_t mode, dev_t rdev)  {  	struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);  	struct inode *inode; @@ -424,27 +482,38 @@ static int hfsplus_mknod(struct inode *dir, struct dentry *dentry,  		init_special_inode(inode, mode, rdev);  	res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode); -	if (res) { -		inode->i_nlink = 0; -		hfsplus_delete_inode(inode); -		iput(inode); -		goto out; +	if (res) +		goto failed_mknod; + +	res = hfsplus_init_inode_security(inode, dir, &dentry->d_name); +	if (res == -EOPNOTSUPP) +		res = 0; /* Operation is not supported. */ +	else if (res) { +		/* Try to delete anyway without error analysis. */ +		hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name); +		goto failed_mknod;  	}  	hfsplus_instantiate(dentry, inode, inode->i_ino);  	mark_inode_dirty(inode); +	goto out; + +failed_mknod: +	clear_nlink(inode); +	hfsplus_delete_inode(inode); +	iput(inode);  out:  	mutex_unlock(&sbi->vh_mutex);  	return res;  } -static int hfsplus_create(struct inode *dir, struct dentry *dentry, int mode, -			  struct nameidata *nd) +static int hfsplus_create(struct inode *dir, struct dentry *dentry, umode_t mode, +			  bool excl)  {  	return hfsplus_mknod(dir, dentry, mode, 0);  } -static int hfsplus_mkdir(struct inode *dir, struct dentry *dentry, int mode) +static int hfsplus_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)  {  	return hfsplus_mknod(dir, dentry, mode | S_IFDIR, 0);  } @@ -473,20 +542,29 @@ static int hfsplus_rename(struct inode *old_dir, struct dentry *old_dentry,  }  const struct inode_operations hfsplus_dir_inode_operations = { -	.lookup		= hfsplus_lookup, -	.create		= hfsplus_create, -	.link		= hfsplus_link, -	.unlink		= hfsplus_unlink, -	.mkdir		= hfsplus_mkdir, -	.rmdir		= hfsplus_rmdir, -	.symlink	= hfsplus_symlink, -	.mknod		= hfsplus_mknod, -	.rename		= hfsplus_rename, +	.lookup			= hfsplus_lookup, +	.create			= hfsplus_create, +	.link			= hfsplus_link, +	.unlink			= hfsplus_unlink, +	.mkdir			= hfsplus_mkdir, +	.rmdir			= hfsplus_rmdir, +	.symlink		= hfsplus_symlink, +	.mknod			= hfsplus_mknod, +	.rename			= hfsplus_rename, +	.setxattr		= generic_setxattr, +	.getxattr		= generic_getxattr, +	.listxattr		= hfsplus_listxattr, +	.removexattr		= generic_removexattr, +#ifdef CONFIG_HFSPLUS_FS_POSIX_ACL +	.get_acl		= hfsplus_get_posix_acl, +	.set_acl		= hfsplus_set_posix_acl, +#endif  };  const struct file_operations hfsplus_dir_operations = { +	.fsync		= hfsplus_file_fsync,  	.read		= generic_read_dir, -	.readdir	= hfsplus_readdir, +	.iterate	= hfsplus_readdir,  	.unlocked_ioctl = hfsplus_ioctl,  	.llseek		= generic_file_llseek,  	.release	= hfsplus_dir_release, diff --git a/fs/hfsplus/extents.c b/fs/hfsplus/extents.c index 0c9cb1820a5..feca524ce2a 100644 --- a/fs/hfsplus/extents.c +++ b/fs/hfsplus/extents.c @@ -83,7 +83,8 @@ static u32 hfsplus_ext_lastblock(struct hfsplus_extent *ext)  	return be32_to_cpu(ext->start_block) + be32_to_cpu(ext->block_count);  } -static void __hfsplus_ext_write_extent(struct inode *inode, struct hfs_find_data *fd) +static int __hfsplus_ext_write_extent(struct inode *inode, +		struct hfs_find_data *fd)  {  	struct hfsplus_inode_info *hip = HFSPLUS_I(inode);  	int res; @@ -94,38 +95,57 @@ static void __hfsplus_ext_write_extent(struct inode *inode, struct hfs_find_data  			      HFSPLUS_IS_RSRC(inode) ?  				HFSPLUS_TYPE_RSRC : HFSPLUS_TYPE_DATA); -	res = hfs_brec_find(fd); -	if (hip->flags & HFSPLUS_FLG_EXT_NEW) { +	res = hfs_brec_find(fd, hfs_find_rec_by_key); +	if (hip->extent_state & HFSPLUS_EXT_NEW) {  		if (res != -ENOENT) -			return; +			return res;  		hfs_brec_insert(fd, hip->cached_extents,  				sizeof(hfsplus_extent_rec)); -		hip->flags &= ~(HFSPLUS_FLG_EXT_DIRTY | HFSPLUS_FLG_EXT_NEW); +		hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW);  	} else {  		if (res) -			return; +			return res;  		hfs_bnode_write(fd->bnode, hip->cached_extents,  				fd->entryoffset, fd->entrylength); -		hip->flags &= ~HFSPLUS_FLG_EXT_DIRTY; +		hip->extent_state &= ~HFSPLUS_EXT_DIRTY;  	} + +	/* +	 * We can't just use hfsplus_mark_inode_dirty here, because we +	 * also get called from hfsplus_write_inode, which should not +	 * redirty the inode.  Instead the callers have to be careful +	 * to explicily mark the inode dirty, too. +	 */ +	set_bit(HFSPLUS_I_EXT_DIRTY, &hip->flags); + +	return 0;  } -static void hfsplus_ext_write_extent_locked(struct inode *inode) +static int hfsplus_ext_write_extent_locked(struct inode *inode)  { -	if (HFSPLUS_I(inode)->flags & HFSPLUS_FLG_EXT_DIRTY) { +	int res = 0; + +	if (HFSPLUS_I(inode)->extent_state & HFSPLUS_EXT_DIRTY) {  		struct hfs_find_data fd; -		hfs_find_init(HFSPLUS_SB(inode->i_sb)->ext_tree, &fd); -		__hfsplus_ext_write_extent(inode, &fd); +		res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->ext_tree, &fd); +		if (res) +			return res; +		res = __hfsplus_ext_write_extent(inode, &fd);  		hfs_find_exit(&fd);  	} +	return res;  } -void hfsplus_ext_write_extent(struct inode *inode) +int hfsplus_ext_write_extent(struct inode *inode)  { +	int res; +  	mutex_lock(&HFSPLUS_I(inode)->extents_lock); -	hfsplus_ext_write_extent_locked(inode); +	res = hfsplus_ext_write_extent_locked(inode);  	mutex_unlock(&HFSPLUS_I(inode)->extents_lock); + +	return res;  }  static inline int __hfsplus_ext_read_extent(struct hfs_find_data *fd, @@ -136,7 +156,7 @@ static inline int __hfsplus_ext_read_extent(struct hfs_find_data *fd,  	hfsplus_ext_build_key(fd->search_key, cnid, block, type);  	fd->key->ext.cnid = 0; -	res = hfs_brec_find(fd); +	res = hfs_brec_find(fd, hfs_find_rec_by_key);  	if (res && res != -ENOENT)  		return res;  	if (fd->key->ext.cnid != fd->search_key->ext.cnid || @@ -144,19 +164,24 @@ static inline int __hfsplus_ext_read_extent(struct hfs_find_data *fd,  		return -ENOENT;  	if (fd->entrylength != sizeof(hfsplus_extent_rec))  		return -EIO; -	hfs_bnode_read(fd->bnode, extent, fd->entryoffset, sizeof(hfsplus_extent_rec)); +	hfs_bnode_read(fd->bnode, extent, fd->entryoffset, +		sizeof(hfsplus_extent_rec));  	return 0;  } -static inline int __hfsplus_ext_cache_extent(struct hfs_find_data *fd, struct inode *inode, u32 block) +static inline int __hfsplus_ext_cache_extent(struct hfs_find_data *fd, +		struct inode *inode, u32 block)  {  	struct hfsplus_inode_info *hip = HFSPLUS_I(inode);  	int res;  	WARN_ON(!mutex_is_locked(&hip->extents_lock)); -	if (hip->flags & HFSPLUS_FLG_EXT_DIRTY) -		__hfsplus_ext_write_extent(inode, fd); +	if (hip->extent_state & HFSPLUS_EXT_DIRTY) { +		res = __hfsplus_ext_write_extent(inode, fd); +		if (res) +			return res; +	}  	res = __hfsplus_ext_read_extent(fd, hip->cached_extents, inode->i_ino,  					block, HFSPLUS_IS_RSRC(inode) ? @@ -164,10 +189,11 @@ static inline int __hfsplus_ext_cache_extent(struct hfs_find_data *fd, struct in  						HFSPLUS_TYPE_DATA);  	if (!res) {  		hip->cached_start = be32_to_cpu(fd->key->ext.start_block); -		hip->cached_blocks = hfsplus_ext_block_count(hip->cached_extents); +		hip->cached_blocks = +			hfsplus_ext_block_count(hip->cached_extents);  	} else {  		hip->cached_start = hip->cached_blocks = 0; -		hip->flags &= ~(HFSPLUS_FLG_EXT_DIRTY | HFSPLUS_FLG_EXT_NEW); +		hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW);  	}  	return res;  } @@ -182,9 +208,11 @@ static int hfsplus_ext_read_extent(struct inode *inode, u32 block)  	    block < hip->cached_start + hip->cached_blocks)  		return 0; -	hfs_find_init(HFSPLUS_SB(inode->i_sb)->ext_tree, &fd); -	res = __hfsplus_ext_cache_extent(&fd, inode, block); -	hfs_find_exit(&fd); +	res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->ext_tree, &fd); +	if (!res) { +		res = __hfsplus_ext_cache_extent(&fd, inode, block); +		hfs_find_exit(&fd); +	}  	return res;  } @@ -197,17 +225,17 @@ int hfsplus_get_block(struct inode *inode, sector_t iblock,  	struct hfsplus_inode_info *hip = HFSPLUS_I(inode);  	int res = -EIO;  	u32 ablock, dblock, mask; -	int shift; +	sector_t sector; +	int was_dirty = 0;  	/* Convert inode block to disk allocation block */ -	shift = sbi->alloc_blksz_shift - sb->s_blocksize_bits;  	ablock = iblock >> sbi->fs_shift;  	if (iblock >= hip->fs_blocks) {  		if (iblock > hip->fs_blocks || !create)  			return -EIO;  		if (ablock >= hip->alloc_blocks) { -			res = hfsplus_file_extend(inode); +			res = hfsplus_file_extend(inode, false);  			if (res)  				return res;  		} @@ -223,27 +251,39 @@ int hfsplus_get_block(struct inode *inode, sector_t iblock,  		return -EIO;  	mutex_lock(&hip->extents_lock); + +	/* +	 * hfsplus_ext_read_extent will write out a cached extent into +	 * the extents btree.  In that case we may have to mark the inode +	 * dirty even for a pure read of an extent here. +	 */ +	was_dirty = (hip->extent_state & HFSPLUS_EXT_DIRTY);  	res = hfsplus_ext_read_extent(inode, ablock); -	if (!res) { -		dblock = hfsplus_ext_find_block(hip->cached_extents, -						ablock - hip->cached_start); -	} else { +	if (res) {  		mutex_unlock(&hip->extents_lock);  		return -EIO;  	} +	dblock = hfsplus_ext_find_block(hip->cached_extents, +					ablock - hip->cached_start);  	mutex_unlock(&hip->extents_lock);  done: -	dprint(DBG_EXTENT, "get_block(%lu): %llu - %u\n", inode->i_ino, (long long)iblock, dblock); +	hfs_dbg(EXTENT, "get_block(%lu): %llu - %u\n", +		inode->i_ino, (long long)iblock, dblock); +  	mask = (1 << sbi->fs_shift) - 1; -	map_bh(bh_result, sb, (dblock << sbi->fs_shift) + sbi->blockoffset + (iblock & mask)); +	sector = ((sector_t)dblock << sbi->fs_shift) + +		  sbi->blockoffset + (iblock & mask); +	map_bh(bh_result, sb, sector); +  	if (create) {  		set_buffer_new(bh_result);  		hip->phys_size += sb->s_blocksize;  		hip->fs_blocks++;  		inode_add_bytes(inode, sb->s_blocksize); -		mark_inode_dirty(inode);  	} +	if (create || was_dirty) +		mark_inode_dirty(inode);  	return 0;  } @@ -251,11 +291,12 @@ static void hfsplus_dump_extent(struct hfsplus_extent *extent)  {  	int i; -	dprint(DBG_EXTENT, "   "); +	hfs_dbg(EXTENT, "   ");  	for (i = 0; i < 8; i++) -		dprint(DBG_EXTENT, " %u:%u", be32_to_cpu(extent[i].start_block), -				 be32_to_cpu(extent[i].block_count)); -	dprint(DBG_EXTENT, "\n"); +		hfs_dbg_cont(EXTENT, " %u:%u", +			     be32_to_cpu(extent[i].start_block), +			     be32_to_cpu(extent[i].block_count)); +	hfs_dbg_cont(EXTENT, "\n");  }  static int hfsplus_add_extent(struct hfsplus_extent *extent, u32 offset, @@ -292,6 +333,7 @@ static int hfsplus_free_extents(struct super_block *sb,  {  	u32 count, start;  	int i; +	int err = 0;  	hfsplus_dump_extent(extent);  	for (i = 0; i < 8; extent++, i++) { @@ -308,25 +350,41 @@ found:  	for (;;) {  		start = be32_to_cpu(extent->start_block);  		if (count <= block_nr) { -			hfsplus_block_free(sb, start, count); +			err = hfsplus_block_free(sb, start, count); +			if (err) { +				pr_err("can't free extent\n"); +				hfs_dbg(EXTENT, " start: %u count: %u\n", +					start, count); +			}  			extent->block_count = 0;  			extent->start_block = 0;  			block_nr -= count;  		} else {  			count -= block_nr; -			hfsplus_block_free(sb, start + count, block_nr); +			err = hfsplus_block_free(sb, start + count, block_nr); +			if (err) { +				pr_err("can't free extent\n"); +				hfs_dbg(EXTENT, " start: %u count: %u\n", +					start, count); +			}  			extent->block_count = cpu_to_be32(count);  			block_nr = 0;  		} -		if (!block_nr || !i) -			return 0; +		if (!block_nr || !i) { +			/* +			 * Try to free all extents and +			 * return only last error +			 */ +			return err; +		}  		i--;  		extent--;  		count = be32_to_cpu(extent->block_count);  	}  } -int hfsplus_free_fork(struct super_block *sb, u32 cnid, struct hfsplus_fork_raw *fork, int type) +int hfsplus_free_fork(struct super_block *sb, u32 cnid, +		struct hfsplus_fork_raw *fork, int type)  {  	struct hfs_find_data fd;  	hfsplus_extent_rec ext_entry; @@ -347,7 +405,9 @@ int hfsplus_free_fork(struct super_block *sb, u32 cnid, struct hfsplus_fork_raw  	if (total_blocks == blocks)  		return 0; -	hfs_find_init(HFSPLUS_SB(sb)->ext_tree, &fd); +	res = hfs_find_init(HFSPLUS_SB(sb)->ext_tree, &fd); +	if (res) +		return res;  	do {  		res = __hfsplus_ext_read_extent(&fd, ext_entry, cnid,  						total_blocks, type); @@ -365,7 +425,7 @@ int hfsplus_free_fork(struct super_block *sb, u32 cnid, struct hfsplus_fork_raw  	return res;  } -int hfsplus_file_extend(struct inode *inode) +int hfsplus_file_extend(struct inode *inode, bool zeroout)  {  	struct super_block *sb = inode->i_sb;  	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); @@ -375,10 +435,10 @@ int hfsplus_file_extend(struct inode *inode)  	if (sbi->alloc_file->i_size * 8 <  	    sbi->total_blocks - sbi->free_blocks + 8) { -		// extend alloc file -		printk(KERN_ERR "hfs: extend alloc file! (%Lu,%u,%u)\n", -				sbi->alloc_file->i_size * 8, -				sbi->total_blocks, sbi->free_blocks); +		/* extend alloc file */ +		pr_err("extend alloc file! (%llu,%u,%u)\n", +		       sbi->alloc_file->i_size * 8, +		       sbi->total_blocks, sbi->free_blocks);  		return -ENOSPC;  	} @@ -402,11 +462,17 @@ int hfsplus_file_extend(struct inode *inode)  		}  	} -	dprint(DBG_EXTENT, "extend %lu: %u,%u\n", inode->i_ino, start, len); +	if (zeroout) { +		res = sb_issue_zeroout(sb, start, len, GFP_NOFS); +		if (res) +			goto out; +	} + +	hfs_dbg(EXTENT, "extend %lu: %u,%u\n", inode->i_ino, start, len);  	if (hip->alloc_blocks <= hip->first_blocks) {  		if (!hip->first_blocks) { -			dprint(DBG_EXTENT, "first extents\n"); +			hfs_dbg(EXTENT, "first extents\n");  			/* no extents yet */  			hip->first_extents[0].start_block = cpu_to_be32(start);  			hip->first_extents[0].block_count = cpu_to_be32(len); @@ -429,28 +495,32 @@ int hfsplus_file_extend(struct inode *inode)  					 start, len);  		if (!res) {  			hfsplus_dump_extent(hip->cached_extents); -			hip->flags |= HFSPLUS_FLG_EXT_DIRTY; +			hip->extent_state |= HFSPLUS_EXT_DIRTY;  			hip->cached_blocks += len;  		} else if (res == -ENOSPC)  			goto insert_extent;  	}  out: -	mutex_unlock(&hip->extents_lock);  	if (!res) {  		hip->alloc_blocks += len; -		mark_inode_dirty(inode); +		mutex_unlock(&hip->extents_lock); +		hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ALLOC_DIRTY); +		return 0;  	} +	mutex_unlock(&hip->extents_lock);  	return res;  insert_extent: -	dprint(DBG_EXTENT, "insert new extent\n"); -	hfsplus_ext_write_extent_locked(inode); +	hfs_dbg(EXTENT, "insert new extent\n"); +	res = hfsplus_ext_write_extent_locked(inode); +	if (res) +		goto out;  	memset(hip->cached_extents, 0, sizeof(hfsplus_extent_rec));  	hip->cached_extents[0].start_block = cpu_to_be32(start);  	hip->cached_extents[0].block_count = cpu_to_be32(len);  	hfsplus_dump_extent(hip->cached_extents); -	hip->flags |= HFSPLUS_FLG_EXT_DIRTY | HFSPLUS_FLG_EXT_NEW; +	hip->extent_state |= HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW;  	hip->cached_start = hip->alloc_blocks;  	hip->cached_blocks = len; @@ -466,22 +536,22 @@ void hfsplus_file_truncate(struct inode *inode)  	u32 alloc_cnt, blk_cnt, start;  	int res; -	dprint(DBG_INODE, "truncate: %lu, %Lu -> %Lu\n", +	hfs_dbg(INODE, "truncate: %lu, %llu -> %llu\n",  		inode->i_ino, (long long)hip->phys_size, inode->i_size);  	if (inode->i_size > hip->phys_size) {  		struct address_space *mapping = inode->i_mapping;  		struct page *page;  		void *fsdata; -		u32 size = inode->i_size; -		int res; +		loff_t size = inode->i_size;  		res = pagecache_write_begin(NULL, mapping, size, 0,  						AOP_FLAG_UNINTERRUPTIBLE,  						&page, &fsdata);  		if (res)  			return; -		res = pagecache_write_end(NULL, mapping, size, 0, 0, page, fsdata); +		res = pagecache_write_end(NULL, mapping, size, +			0, 0, page, fsdata);  		if (res < 0)  			return;  		mark_inode_dirty(inode); @@ -491,12 +561,19 @@ void hfsplus_file_truncate(struct inode *inode)  	blk_cnt = (inode->i_size + HFSPLUS_SB(sb)->alloc_blksz - 1) >>  			HFSPLUS_SB(sb)->alloc_blksz_shift; + +	mutex_lock(&hip->extents_lock); +  	alloc_cnt = hip->alloc_blocks;  	if (blk_cnt == alloc_cnt) -		goto out; +		goto out_unlock; -	mutex_lock(&hip->extents_lock); -	hfs_find_init(HFSPLUS_SB(sb)->ext_tree, &fd); +	res = hfs_find_init(HFSPLUS_SB(sb)->ext_tree, &fd); +	if (res) { +		mutex_unlock(&hip->extents_lock); +		/* XXX: We lack error handling of hfsplus_file_truncate() */ +		return; +	}  	while (1) {  		if (alloc_cnt == hip->first_blocks) {  			hfsplus_free_extents(sb, hip->first_extents, @@ -513,21 +590,22 @@ void hfsplus_file_truncate(struct inode *inode)  				     alloc_cnt - start, alloc_cnt - blk_cnt);  		hfsplus_dump_extent(hip->cached_extents);  		if (blk_cnt > start) { -			hip->flags |= HFSPLUS_FLG_EXT_DIRTY; +			hip->extent_state |= HFSPLUS_EXT_DIRTY;  			break;  		}  		alloc_cnt = start;  		hip->cached_start = hip->cached_blocks = 0; -		hip->flags &= ~(HFSPLUS_FLG_EXT_DIRTY | HFSPLUS_FLG_EXT_NEW); +		hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW);  		hfs_brec_remove(&fd);  	}  	hfs_find_exit(&fd); -	mutex_unlock(&hip->extents_lock);  	hip->alloc_blocks = blk_cnt; -out: +out_unlock: +	mutex_unlock(&hip->extents_lock);  	hip->phys_size = inode->i_size; -	hip->fs_blocks = (inode->i_size + sb->s_blocksize - 1) >> sb->s_blocksize_bits; +	hip->fs_blocks = (inode->i_size + sb->s_blocksize - 1) >> +		sb->s_blocksize_bits;  	inode_set_bytes(inode, hip->fs_blocks << sb->s_blocksize_bits); -	mark_inode_dirty(inode); +	hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ALLOC_DIRTY);  } diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h index cb3653efb57..eb5e059f481 100644 --- a/fs/hfsplus/hfsplus_fs.h +++ b/fs/hfsplus/hfsplus_fs.h @@ -10,9 +10,16 @@  #ifndef _LINUX_HFSPLUS_FS_H  #define _LINUX_HFSPLUS_FS_H +#ifdef pr_fmt +#undef pr_fmt +#endif + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #include <linux/fs.h>  #include <linux/mutex.h>  #include <linux/buffer_head.h> +#include <linux/blkdev.h>  #include "hfsplus_raw.h"  #define DBG_BNODE_REFS	0x00000001 @@ -22,14 +29,27 @@  #define DBG_SUPER	0x00000010  #define DBG_EXTENT	0x00000020  #define DBG_BITMAP	0x00000040 +#define DBG_ATTR_MOD	0x00000080 +#define DBG_ACL_MOD	0x00000100 -//#define DBG_MASK	(DBG_EXTENT|DBG_INODE|DBG_BNODE_MOD) -//#define DBG_MASK	(DBG_BNODE_MOD|DBG_CAT_MOD|DBG_INODE) -//#define DBG_MASK	(DBG_CAT_MOD|DBG_BNODE_REFS|DBG_INODE|DBG_EXTENT) +#if 0 +#define DBG_MASK	(DBG_EXTENT|DBG_INODE|DBG_BNODE_MOD) +#define DBG_MASK	(DBG_BNODE_MOD|DBG_CAT_MOD|DBG_INODE) +#define DBG_MASK	(DBG_CAT_MOD|DBG_BNODE_REFS|DBG_INODE|DBG_EXTENT) +#endif  #define DBG_MASK	(0) -#define dprint(flg, fmt, args...) \ -	if (flg & DBG_MASK) printk(fmt , ## args) +#define hfs_dbg(flg, fmt, ...)					\ +do {								\ +	if (DBG_##flg & DBG_MASK)				\ +		printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__);	\ +} while (0) + +#define hfs_dbg_cont(flg, fmt, ...)				\ +do {								\ +	if (DBG_##flg & DBG_MASK)				\ +		pr_cont(fmt, ##__VA_ARGS__);			\ +} while (0)  /* Runtime config options */  #define HFSPLUS_DEF_CR_TYPE    0x3F3F3F3F  /* '????' */ @@ -37,10 +57,18 @@  #define HFSPLUS_TYPE_DATA 0x00  #define HFSPLUS_TYPE_RSRC 0xFF -typedef int (*btree_keycmp)(const hfsplus_btree_key *, const hfsplus_btree_key *); +typedef int (*btree_keycmp)(const hfsplus_btree_key *, +		const hfsplus_btree_key *);  #define NODE_HASH_SIZE	256 +/* B-tree mutex nested subclasses */ +enum hfsplus_btree_mutex_classes { +	CATALOG_BTREE_MUTEX, +	EXTENTS_BTREE_MUTEX, +	ATTR_BTREE_MUTEX, +}; +  /* An HFS+ BTree held in memory */  struct hfs_btree {  	struct super_block *sb; @@ -61,7 +89,6 @@ struct hfs_btree {  	unsigned int max_key_len;  	unsigned int depth; -	//unsigned int map1_size, map_size;  	struct mutex tree_lock;  	unsigned int pages_per_bnode; @@ -100,6 +127,14 @@ struct hfs_bnode {  #define HFS_BNODE_DELETED	4  /* + * Attributes file states + */ +#define HFSPLUS_EMPTY_ATTR_TREE		0 +#define HFSPLUS_CREATING_ATTR_TREE	1 +#define HFSPLUS_VALID_ATTR_TREE		2 +#define HFSPLUS_FAILED_ATTR_TREE	3 + +/*   * HFS+ superblock info (built from Volume Header on disk)   */ @@ -107,18 +142,22 @@ struct hfsplus_vh;  struct hfs_btree;  struct hfsplus_sb_info { -	struct buffer_head *s_vhbh; +	void *s_vhdr_buf;  	struct hfsplus_vh *s_vhdr; +	void *s_backup_vhdr_buf; +	struct hfsplus_vh *s_backup_vhdr;  	struct hfs_btree *ext_tree;  	struct hfs_btree *cat_tree;  	struct hfs_btree *attr_tree; +	atomic_t attr_tree_state;  	struct inode *alloc_file;  	struct inode *hidden_dir;  	struct nls_table *nls;  	/* Runtime variables */  	u32 blockoffset; -	u32 sect_count; +	sector_t part_start; +	sector_t sect_count;  	int fs_shift;  	/* immutable data from the volume header */ @@ -142,12 +181,15 @@ struct hfsplus_sb_info {  	u32 type;  	umode_t umask; -	uid_t uid; -	gid_t gid; +	kuid_t uid; +	kgid_t gid;  	int part, session; -  	unsigned long flags; + +	int work_queued;               /* non-zero delayed work is queued */ +	struct delayed_work sync_work; /* FS sync delayed work */ +	spinlock_t work_lock;          /* protects sync_work and work_queued */  };  #define HFSPLUS_SB_WRITEBACKUP	0 @@ -155,6 +197,12 @@ struct hfsplus_sb_info {  #define HFSPLUS_SB_FORCE	2  #define HFSPLUS_SB_HFSX		3  #define HFSPLUS_SB_CASEFOLD	4 +#define HFSPLUS_SB_NOBARRIER	5 + +static inline struct hfsplus_sb_info *HFSPLUS_SB(struct super_block *sb) +{ +	return sb->s_fs_info; +}  struct hfsplus_inode_info { @@ -170,7 +218,7 @@ struct hfsplus_inode_info {  	u32 cached_blocks;  	hfsplus_extent_rec first_extents;  	hfsplus_extent_rec cached_extents; -	unsigned long flags; +	unsigned int extent_state;  	struct mutex extents_lock;  	/* @@ -185,22 +233,51 @@ struct hfsplus_inode_info {  	u32 linkid;  	/* +	 * Accessed using atomic bitops. +	 */ +	unsigned long flags; + +	/*  	 * Protected by i_mutex.  	 */  	sector_t fs_blocks;  	u8 userflags;		/* BSD user file flags */ +	u32 subfolders;		/* Subfolder count (HFSX only) */  	struct list_head open_dir_list;  	loff_t phys_size;  	struct inode vfs_inode;  }; -#define HFSPLUS_FLG_RSRC	0x0001 -#define HFSPLUS_FLG_EXT_DIRTY	0x0002 -#define HFSPLUS_FLG_EXT_NEW	0x0004 +#define HFSPLUS_EXT_DIRTY	0x0001 +#define HFSPLUS_EXT_NEW		0x0002 + +#define HFSPLUS_I_RSRC		0	/* represents a resource fork */ +#define HFSPLUS_I_CAT_DIRTY	1	/* has changes in the catalog tree */ +#define HFSPLUS_I_EXT_DIRTY	2	/* has changes in the extent tree */ +#define HFSPLUS_I_ALLOC_DIRTY	3	/* has changes in the allocation file */ +#define HFSPLUS_I_ATTR_DIRTY	4	/* has changes in the attributes tree */ + +#define HFSPLUS_IS_RSRC(inode) \ +	test_bit(HFSPLUS_I_RSRC, &HFSPLUS_I(inode)->flags) + +static inline struct hfsplus_inode_info *HFSPLUS_I(struct inode *inode) +{ +	return list_entry(inode, struct hfsplus_inode_info, vfs_inode); +} -#define HFSPLUS_IS_DATA(inode)   (!(HFSPLUS_I(inode)->flags & HFSPLUS_FLG_RSRC)) -#define HFSPLUS_IS_RSRC(inode)   (HFSPLUS_I(inode)->flags & HFSPLUS_FLG_RSRC) +/* + * Mark an inode dirty, and also mark the btree in which the + * specific type of metadata is stored. + * For data or metadata that gets written back by into the catalog btree + * by hfsplus_write_inode a plain mark_inode_dirty call is enough. + */ +static inline void hfsplus_mark_inode_dirty(struct inode *inode, +		unsigned int flag) +{ +	set_bit(flag, &HFSPLUS_I(inode)->flags); +	mark_inode_dirty(inode); +}  struct hfs_find_data {  	/* filled by caller */ @@ -221,6 +298,15 @@ struct hfsplus_readdir_data {  	struct hfsplus_cat_key key;  }; +/* + * Find minimum acceptible I/O size for an hfsplus sb. + */ +static inline unsigned short hfsplus_min_io_size(struct super_block *sb) +{ +	return max_t(unsigned short, bdev_logical_block_size(sb->s_bdev), +		     HFSPLUS_SECTOR_SIZE); +} +  #define hfs_btree_open hfsplus_btree_open  #define hfs_btree_close hfsplus_btree_close  #define hfs_btree_write hfsplus_btree_write @@ -250,7 +336,7 @@ struct hfsplus_readdir_data {  #define hfs_brec_remove hfsplus_brec_remove  #define hfs_find_init hfsplus_find_init  #define hfs_find_exit hfsplus_find_exit -#define __hfs_brec_find __hplusfs_brec_find +#define __hfs_brec_find __hfsplus_brec_find  #define hfs_brec_find hfsplus_brec_find  #define hfs_brec_read hfsplus_brec_read  #define hfs_brec_goto hfsplus_brec_goto @@ -268,106 +354,153 @@ struct hfsplus_readdir_data {  /* + * hfs+-specific ioctl for making the filesystem bootable + */ +#define HFSPLUS_IOC_BLESS _IO('h', 0x80) + +typedef int (*search_strategy_t)(struct hfs_bnode *, +				struct hfs_find_data *, +				int *, int *, int *); + +/*   * Functions in any *.c used in other files   */ +/* attributes.c */ +int __init hfsplus_create_attr_tree_cache(void); +void hfsplus_destroy_attr_tree_cache(void); +int hfsplus_attr_bin_cmp_key(const hfsplus_btree_key *k1, +			     const hfsplus_btree_key *k2); +int hfsplus_attr_build_key(struct super_block *sb, hfsplus_btree_key *key, +			   u32 cnid, const char *name); +hfsplus_attr_entry *hfsplus_alloc_attr_entry(void); +void hfsplus_destroy_attr_entry(hfsplus_attr_entry *entry); +int hfsplus_find_attr(struct super_block *sb, u32 cnid, const char *name, +		      struct hfs_find_data *fd); +int hfsplus_attr_exists(struct inode *inode, const char *name); +int hfsplus_create_attr(struct inode *inode, const char *name, +			const void *value, size_t size); +int hfsplus_delete_attr(struct inode *inode, const char *name); +int hfsplus_delete_all_attrs(struct inode *dir, u32 cnid); +  /* bitmap.c */ -int hfsplus_block_allocate(struct super_block *, u32, u32, u32 *); -int hfsplus_block_free(struct super_block *, u32, u32); +int hfsplus_block_allocate(struct super_block *sb, u32 size, u32 offset, +			   u32 *max); +int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count);  /* btree.c */ -struct hfs_btree *hfs_btree_open(struct super_block *, u32); -void hfs_btree_close(struct hfs_btree *); -void hfs_btree_write(struct hfs_btree *); -struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *); -void hfs_bmap_free(struct hfs_bnode *); +u32 hfsplus_calc_btree_clump_size(u32 block_size, u32 node_size, u64 sectors, +				  int file_id); +struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id); +void hfs_btree_close(struct hfs_btree *tree); +int hfs_btree_write(struct hfs_btree *tree); +struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree); +void hfs_bmap_free(struct hfs_bnode *node);  /* bnode.c */ -void hfs_bnode_read(struct hfs_bnode *, void *, int, int); -u16 hfs_bnode_read_u16(struct hfs_bnode *, int); -u8 hfs_bnode_read_u8(struct hfs_bnode *, int); -void hfs_bnode_read_key(struct hfs_bnode *, void *, int); -void hfs_bnode_write(struct hfs_bnode *, void *, int, int); -void hfs_bnode_write_u16(struct hfs_bnode *, int, u16); -void hfs_bnode_clear(struct hfs_bnode *, int, int); -void hfs_bnode_copy(struct hfs_bnode *, int, -		    struct hfs_bnode *, int, int); -void hfs_bnode_move(struct hfs_bnode *, int, int, int); -void hfs_bnode_dump(struct hfs_bnode *); -void hfs_bnode_unlink(struct hfs_bnode *); -struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *, u32); -struct hfs_bnode *hfs_bnode_find(struct hfs_btree *, u32); -void hfs_bnode_unhash(struct hfs_bnode *); -void hfs_bnode_free(struct hfs_bnode *); -struct hfs_bnode *hfs_bnode_create(struct hfs_btree *, u32); -void hfs_bnode_get(struct hfs_bnode *); -void hfs_bnode_put(struct hfs_bnode *); +void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len); +u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off); +u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off); +void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off); +void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len); +void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data); +void hfs_bnode_clear(struct hfs_bnode *node, int off, int len); +void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst, +		    struct hfs_bnode *src_node, int src, int len); +void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len); +void hfs_bnode_dump(struct hfs_bnode *node); +void hfs_bnode_unlink(struct hfs_bnode *node); +struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid); +void hfs_bnode_unhash(struct hfs_bnode *node); +struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num); +void hfs_bnode_free(struct hfs_bnode *node); +struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num); +void hfs_bnode_get(struct hfs_bnode *node); +void hfs_bnode_put(struct hfs_bnode *node); +bool hfs_bnode_need_zeroout(struct hfs_btree *tree);  /* brec.c */ -u16 hfs_brec_lenoff(struct hfs_bnode *, u16, u16 *); -u16 hfs_brec_keylen(struct hfs_bnode *, u16); -int hfs_brec_insert(struct hfs_find_data *, void *, int); -int hfs_brec_remove(struct hfs_find_data *); +u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off); +u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec); +int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len); +int hfs_brec_remove(struct hfs_find_data *fd);  /* bfind.c */ -int hfs_find_init(struct hfs_btree *, struct hfs_find_data *); -void hfs_find_exit(struct hfs_find_data *); -int __hfs_brec_find(struct hfs_bnode *, struct hfs_find_data *); -int hfs_brec_find(struct hfs_find_data *); -int hfs_brec_read(struct hfs_find_data *, void *, int); -int hfs_brec_goto(struct hfs_find_data *, int); +int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd); +void hfs_find_exit(struct hfs_find_data *fd); +int hfs_find_1st_rec_by_cnid(struct hfs_bnode *bnode, struct hfs_find_data *fd, +			     int *begin, int *end, int *cur_rec); +int hfs_find_rec_by_key(struct hfs_bnode *bnode, struct hfs_find_data *fd, +			int *begin, int *end, int *cur_rec); +int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd, +		    search_strategy_t rec_found); +int hfs_brec_find(struct hfs_find_data *fd, search_strategy_t do_key_compare); +int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len); +int hfs_brec_goto(struct hfs_find_data *fd, int cnt);  /* catalog.c */ -int hfsplus_cat_case_cmp_key(const hfsplus_btree_key *, const hfsplus_btree_key *); -int hfsplus_cat_bin_cmp_key(const hfsplus_btree_key *, const hfsplus_btree_key *); -void hfsplus_cat_build_key(struct super_block *sb, hfsplus_btree_key *, u32, struct qstr *); -int hfsplus_find_cat(struct super_block *, u32, struct hfs_find_data *); -int hfsplus_create_cat(u32, struct inode *, struct qstr *, struct inode *); -int hfsplus_delete_cat(u32, struct inode *, struct qstr *); -int hfsplus_rename_cat(u32, struct inode *, struct qstr *, -		       struct inode *, struct qstr *); +int hfsplus_cat_case_cmp_key(const hfsplus_btree_key *k1, +			     const hfsplus_btree_key *k2); +int hfsplus_cat_bin_cmp_key(const hfsplus_btree_key *k1, +			    const hfsplus_btree_key *k2); +void hfsplus_cat_build_key(struct super_block *sb, hfsplus_btree_key *key, +			   u32 parent, struct qstr *str);  void hfsplus_cat_set_perms(struct inode *inode, struct hfsplus_perm *perms); +int hfsplus_find_cat(struct super_block *sb, u32 cnid, +		     struct hfs_find_data *fd); +int hfsplus_create_cat(u32 cnid, struct inode *dir, struct qstr *str, +		       struct inode *inode); +int hfsplus_delete_cat(u32 cnid, struct inode *dir, struct qstr *str); +int hfsplus_rename_cat(u32 cnid, struct inode *src_dir, struct qstr *src_name, +		       struct inode *dst_dir, struct qstr *dst_name);  /* dir.c */  extern const struct inode_operations hfsplus_dir_inode_operations;  extern const struct file_operations hfsplus_dir_operations;  /* extents.c */ -int hfsplus_ext_cmp_key(const hfsplus_btree_key *, const hfsplus_btree_key *); -void hfsplus_ext_write_extent(struct inode *); -int hfsplus_get_block(struct inode *, sector_t, struct buffer_head *, int); -int hfsplus_free_fork(struct super_block *, u32, struct hfsplus_fork_raw *, int); -int hfsplus_file_extend(struct inode *); -void hfsplus_file_truncate(struct inode *); +int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1, +			const hfsplus_btree_key *k2); +int hfsplus_ext_write_extent(struct inode *inode); +int hfsplus_get_block(struct inode *inode, sector_t iblock, +		      struct buffer_head *bh_result, int create); +int hfsplus_free_fork(struct super_block *sb, u32 cnid, +		      struct hfsplus_fork_raw *fork, int type); +int hfsplus_file_extend(struct inode *inode, bool zeroout); +void hfsplus_file_truncate(struct inode *inode);  /* inode.c */  extern const struct address_space_operations hfsplus_aops;  extern const struct address_space_operations hfsplus_btree_aops;  extern const struct dentry_operations hfsplus_dentry_operations; -void hfsplus_inode_read_fork(struct inode *, struct hfsplus_fork_raw *); -void hfsplus_inode_write_fork(struct inode *, struct hfsplus_fork_raw *); -int hfsplus_cat_read_inode(struct inode *, struct hfs_find_data *); -int hfsplus_cat_write_inode(struct inode *); -struct inode *hfsplus_new_inode(struct super_block *, int); -void hfsplus_delete_inode(struct inode *); +struct inode *hfsplus_new_inode(struct super_block *sb, umode_t mode); +void hfsplus_delete_inode(struct inode *inode); +void hfsplus_inode_read_fork(struct inode *inode, +			     struct hfsplus_fork_raw *fork); +void hfsplus_inode_write_fork(struct inode *inode, +			      struct hfsplus_fork_raw *fork); +int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd); +int hfsplus_cat_write_inode(struct inode *inode); +int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end, +		       int datasync);  /* ioctl.c */  long hfsplus_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); -int hfsplus_setxattr(struct dentry *dentry, const char *name, -		     const void *value, size_t size, int flags); -ssize_t hfsplus_getxattr(struct dentry *dentry, const char *name, -			 void *value, size_t size); -ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size);  /* options.c */ -int hfsplus_parse_options(char *, struct hfsplus_sb_info *); -void hfsplus_fill_defaults(struct hfsplus_sb_info *); -int hfsplus_show_options(struct seq_file *, struct vfsmount *); +void hfsplus_fill_defaults(struct hfsplus_sb_info *opts); +int hfsplus_parse_options_remount(char *input, int *force); +int hfsplus_parse_options(char *input, struct hfsplus_sb_info *sbi); +int hfsplus_show_options(struct seq_file *seq, struct dentry *root); + +/* part_tbl.c */ +int hfs_part_find(struct super_block *sb, sector_t *part_start, +		  sector_t *part_size);  /* super.c */ -struct inode *hfsplus_iget(struct super_block *, unsigned long); -int hfsplus_sync_fs(struct super_block *sb, int wait); +struct inode *hfsplus_iget(struct super_block *sb, unsigned long ino); +void hfsplus_mark_mdb_dirty(struct super_block *sb);  /* tables.c */  extern u16 hfsplus_case_fold_table[]; @@ -375,45 +508,23 @@ extern u16 hfsplus_decompose_table[];  extern u16 hfsplus_compose_table[];  /* unicode.c */ -int hfsplus_strcasecmp(const struct hfsplus_unistr *, const struct hfsplus_unistr *); -int hfsplus_strcmp(const struct hfsplus_unistr *, const struct hfsplus_unistr *); -int hfsplus_uni2asc(struct super_block *, const struct hfsplus_unistr *, char *, int *); -int hfsplus_asc2uni(struct super_block *, struct hfsplus_unistr *, const char *, int); -int hfsplus_hash_dentry(struct dentry *dentry, struct qstr *str); -int hfsplus_compare_dentry(struct dentry *dentry, struct qstr *s1, struct qstr *s2); +int hfsplus_strcasecmp(const struct hfsplus_unistr *s1, +		       const struct hfsplus_unistr *s2); +int hfsplus_strcmp(const struct hfsplus_unistr *s1, +		   const struct hfsplus_unistr *s2); +int hfsplus_uni2asc(struct super_block *sb, const struct hfsplus_unistr *ustr, +		    char *astr, int *len_p); +int hfsplus_asc2uni(struct super_block *sb, struct hfsplus_unistr *ustr, +		    int max_unistr_len, const char *astr, int len); +int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str); +int hfsplus_compare_dentry(const struct dentry *parent, +			   const struct dentry *dentry, unsigned int len, +			   const char *str, const struct qstr *name);  /* wrapper.c */ -int hfsplus_read_wrapper(struct super_block *); - -int hfs_part_find(struct super_block *, sector_t *, sector_t *); - -/* access macros */ -static inline struct hfsplus_sb_info *HFSPLUS_SB(struct super_block *sb) -{ -	return sb->s_fs_info; -} - -static inline struct hfsplus_inode_info *HFSPLUS_I(struct inode *inode) -{ -	return list_entry(inode, struct hfsplus_inode_info, vfs_inode); -} - -#define sb_bread512(sb, sec, data) ({			\ -	struct buffer_head *__bh;			\ -	sector_t __block;				\ -	loff_t __start;					\ -	int __offset;					\ -							\ -	__start = (loff_t)(sec) << HFSPLUS_SECTOR_SHIFT;\ -	__block = __start >> (sb)->s_blocksize_bits;	\ -	__offset = __start & ((sb)->s_blocksize - 1);	\ -	__bh = sb_bread((sb), __block);			\ -	if (likely(__bh != NULL))			\ -		data = (void *)(__bh->b_data + __offset);\ -	else						\ -		data = NULL;				\ -	__bh;						\ -}) +int hfsplus_submit_bio(struct super_block *sb, sector_t sector, void *buf, +		       void **data, int rw); +int hfsplus_read_wrapper(struct super_block *sb);  /* time macros */  #define __hfsp_mt2ut(t)		(be32_to_cpu(t) - 2082844800U) diff --git a/fs/hfsplus/hfsplus_raw.h b/fs/hfsplus/hfsplus_raw.h index 6892899fd6f..8298d0985f8 100644 --- a/fs/hfsplus/hfsplus_raw.h +++ b/fs/hfsplus/hfsplus_raw.h @@ -36,7 +36,8 @@  #define HFSP_WRAPOFF_EMBEDSIG     0x7C  #define HFSP_WRAPOFF_EMBEDEXT     0x7E -#define HFSP_HIDDENDIR_NAME	"\xe2\x90\x80\xe2\x90\x80\xe2\x90\x80\xe2\x90\x80HFS+ Private Data" +#define HFSP_HIDDENDIR_NAME \ +	"\xe2\x90\x80\xe2\x90\x80\xe2\x90\x80\xe2\x90\x80HFS+ Private Data"  #define HFSP_HARDLINK_TYPE	0x686c6e6b	/* 'hlnk' */  #define HFSP_HFSPLUS_CREATOR	0x6866732b	/* 'hfs+' */ @@ -51,13 +52,23 @@  typedef __be32 hfsplus_cnid;  typedef __be16 hfsplus_unichr; +#define HFSPLUS_MAX_STRLEN 255 +#define HFSPLUS_ATTR_MAX_STRLEN 127 +  /* A "string" as used in filenames, etc. */  struct hfsplus_unistr {  	__be16 length; -	hfsplus_unichr unicode[255]; +	hfsplus_unichr unicode[HFSPLUS_MAX_STRLEN];  } __packed; -#define HFSPLUS_MAX_STRLEN 255 +/* + * A "string" is used in attributes file + * for name of extended attribute + */ +struct hfsplus_attr_unistr { +	__be16 length; +	hfsplus_unichr unicode[HFSPLUS_ATTR_MAX_STRLEN]; +} __packed;  /* POSIX permissions */  struct hfsplus_perm { @@ -116,7 +127,7 @@ struct hfsplus_vh {  	__be32 write_count;  	__be64 encodings_bmp; -	u8 finder_info[32]; +	u32 finder_info[8];  	struct hfsplus_fork_raw alloc_file;  	struct hfsplus_fork_raw ext_file; @@ -133,6 +144,7 @@ struct hfsplus_vh {  #define HFSPLUS_VOL_NODEID_REUSED	(1 << 12)  #define HFSPLUS_VOL_JOURNALED		(1 << 13)  #define HFSPLUS_VOL_SOFTLOCK		(1 << 15) +#define HFSPLUS_VOL_UNUSED_NODE_FIX	(1 << 31)  /* HFS+ BTree node descriptor */  struct hfs_bnode_desc { @@ -145,10 +157,10 @@ struct hfs_bnode_desc {  } __packed;  /* HFS+ BTree node types */ -#define HFS_NODE_INDEX	0x00 -#define HFS_NODE_HEADER	0x01 -#define HFS_NODE_MAP	0x02 -#define HFS_NODE_LEAF	0xFF +#define HFS_NODE_INDEX	0x00	/* An internal (index) node */ +#define HFS_NODE_HEADER	0x01	/* The tree header node (node 0) */ +#define HFS_NODE_MAP	0x02	/* Holds part of the bitmap of used nodes */ +#define HFS_NODE_LEAF	0xFF	/* A leaf (ndNHeight==1) node */  /* HFS+ BTree header */  struct hfs_btree_header_rec { @@ -176,6 +188,9 @@ struct hfs_btree_header_rec {  /* HFS+ BTree misc info */  #define HFSPLUS_TREE_HEAD 0  #define HFSPLUS_NODE_MXSZ 32768 +#define HFSPLUS_ATTR_TREE_NODE_SIZE		8192 +#define HFSPLUS_BTREE_HDR_NODE_RECS_COUNT	3 +#define HFSPLUS_BTREE_HDR_USER_BYTES		128  /* Some special File ID numbers (stolen from hfs.h) */  #define HFSPLUS_POR_CNID		1	/* Parent Of the Root */ @@ -247,7 +262,7 @@ struct hfsplus_cat_folder {  	struct DInfo user_info;  	struct DXInfo finder_info;  	__be32 text_encoding; -	u32 reserved; +	__be32 subfolders;	/* Subfolder count in HFSX. Reserved in HFS+. */  } __packed;  /* HFS file info (stolen from hfs.h) */ @@ -287,9 +302,13 @@ struct hfsplus_cat_file {  	struct hfsplus_fork_raw rsrc_fork;  } __packed; -/* File attribute bits */ +/* File and folder flag bits */  #define HFSPLUS_FILE_LOCKED		0x0001  #define HFSPLUS_FILE_THREAD_EXISTS	0x0002 +#define HFSPLUS_XATTR_EXISTS		0x0004 +#define HFSPLUS_ACL_EXISTS		0x0008 +#define HFSPLUS_HAS_FOLDER_COUNT	0x0010	/* Folder has subfolder count +						 * (HFSX only) */  /* HFS+ catalog thread (part of a cat_entry) */  struct hfsplus_cat_thread { @@ -326,11 +345,63 @@ struct hfsplus_ext_key {  #define HFSPLUS_EXT_KEYLEN	sizeof(struct hfsplus_ext_key) +#define HFSPLUS_XATTR_FINDER_INFO_NAME "com.apple.FinderInfo" +#define HFSPLUS_XATTR_ACL_NAME "com.apple.system.Security" + +#define HFSPLUS_ATTR_INLINE_DATA 0x10 +#define HFSPLUS_ATTR_FORK_DATA   0x20 +#define HFSPLUS_ATTR_EXTENTS     0x30 + +/* HFS+ attributes tree key */ +struct hfsplus_attr_key { +	__be16 key_len; +	__be16 pad; +	hfsplus_cnid cnid; +	__be32 start_block; +	struct hfsplus_attr_unistr key_name; +} __packed; + +#define HFSPLUS_ATTR_KEYLEN	sizeof(struct hfsplus_attr_key) + +/* HFS+ fork data attribute */ +struct hfsplus_attr_fork_data { +	__be32 record_type; +	__be32 reserved; +	struct hfsplus_fork_raw the_fork; +} __packed; + +/* HFS+ extension attribute */ +struct hfsplus_attr_extents { +	__be32 record_type; +	__be32 reserved; +	struct hfsplus_extent extents; +} __packed; + +#define HFSPLUS_MAX_INLINE_DATA_SIZE 3802 + +/* HFS+ attribute inline data */ +struct hfsplus_attr_inline_data { +	__be32 record_type; +	__be32 reserved1; +	u8 reserved2[6]; +	__be16 length; +	u8 raw_bytes[HFSPLUS_MAX_INLINE_DATA_SIZE]; +} __packed; + +/* A data record in the attributes tree */ +typedef union { +	__be32 record_type; +	struct hfsplus_attr_fork_data fork_data; +	struct hfsplus_attr_extents extents; +	struct hfsplus_attr_inline_data inline_data; +} __packed hfsplus_attr_entry; +  /* HFS+ generic BTree key */  typedef union {  	__be16 key_len;  	struct hfsplus_cat_key cat;  	struct hfsplus_ext_key ext; +	struct hfsplus_attr_key attr;  } __packed hfsplus_btree_key;  #endif diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index 8afd7e84f98..0cf786f2d04 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -8,14 +8,18 @@   * Inode handling routines   */ +#include <linux/blkdev.h>  #include <linux/mm.h>  #include <linux/fs.h>  #include <linux/pagemap.h>  #include <linux/mpage.h>  #include <linux/sched.h> +#include <linux/aio.h>  #include "hfsplus_fs.h"  #include "hfsplus_raw.h" +#include "xattr.h" +#include "acl.h"  static int hfsplus_readpage(struct file *file, struct page *page)  { @@ -27,6 +31,16 @@ static int hfsplus_writepage(struct page *page, struct writeback_control *wbc)  	return block_write_full_page(page, hfsplus_get_block, wbc);  } +static void hfsplus_write_failed(struct address_space *mapping, loff_t to) +{ +	struct inode *inode = mapping->host; + +	if (to > inode->i_size) { +		truncate_pagecache(inode, inode->i_size); +		hfsplus_file_truncate(inode); +	} +} +  static int hfsplus_write_begin(struct file *file, struct address_space *mapping,  			loff_t pos, unsigned len, unsigned flags,  			struct page **pagep, void **fsdata) @@ -37,11 +51,8 @@ static int hfsplus_write_begin(struct file *file, struct address_space *mapping,  	ret = cont_write_begin(file, mapping, pos, len, flags, pagep, fsdata,  				hfsplus_get_block,  				&HFSPLUS_I(mapping->host)->phys_size); -	if (unlikely(ret)) { -		loff_t isize = mapping->host->i_size; -		if (pos + len > isize) -			vmtruncate(mapping->host, isize); -	} +	if (unlikely(ret)) +		hfsplus_write_failed(mapping, pos + len);  	return ret;  } @@ -77,7 +88,8 @@ static int hfsplus_releasepage(struct page *page, gfp_t mask)  	if (!tree)  		return 0;  	if (tree->node_size >= PAGE_CACHE_SIZE) { -		nidx = page->index >> (tree->node_size_shift - PAGE_CACHE_SHIFT); +		nidx = page->index >> +			(tree->node_size_shift - PAGE_CACHE_SHIFT);  		spin_lock(&tree->hash_lock);  		node = hfs_bnode_findhash(tree, nidx);  		if (!node) @@ -90,7 +102,8 @@ static int hfsplus_releasepage(struct page *page, gfp_t mask)  		}  		spin_unlock(&tree->hash_lock);  	} else { -		nidx = page->index << (PAGE_CACHE_SHIFT - tree->node_size_shift); +		nidx = page->index << +			(PAGE_CACHE_SHIFT - tree->node_size_shift);  		i = 1 << (PAGE_CACHE_SHIFT - tree->node_size_shift);  		spin_lock(&tree->hash_lock);  		do { @@ -110,14 +123,16 @@ static int hfsplus_releasepage(struct page *page, gfp_t mask)  }  static ssize_t hfsplus_direct_IO(int rw, struct kiocb *iocb, -		const struct iovec *iov, loff_t offset, unsigned long nr_segs) +		struct iov_iter *iter, loff_t offset)  {  	struct file *file = iocb->ki_filp; -	struct inode *inode = file->f_path.dentry->d_inode->i_mapping->host; +	struct address_space *mapping = file->f_mapping; +	struct inode *inode = file_inode(file)->i_mapping->host; +	size_t count = iov_iter_count(iter);  	ssize_t ret; -	ret = blockdev_direct_IO(rw, iocb, inode, inode->i_sb->s_bdev, iov, -				  offset, nr_segs, hfsplus_get_block, NULL); +	ret = blockdev_direct_IO(rw, iocb, inode, iter, offset,  +				 hfsplus_get_block);  	/*  	 * In case of error extending write may have instantiated a few @@ -125,10 +140,10 @@ static ssize_t hfsplus_direct_IO(int rw, struct kiocb *iocb,  	 */  	if (unlikely((rw & WRITE) && ret < 0)) {  		loff_t isize = i_size_read(inode); -		loff_t end = offset + iov_length(iov, nr_segs); +		loff_t end = offset + count;  		if (end > isize) -			vmtruncate(inode, isize); +			hfsplus_write_failed(mapping, end);  	}  	return ret; @@ -143,7 +158,6 @@ static int hfsplus_writepages(struct address_space *mapping,  const struct address_space_operations hfsplus_btree_aops = {  	.readpage	= hfsplus_readpage,  	.writepage	= hfsplus_writepage, -	.sync_page	= block_sync_page,  	.write_begin	= hfsplus_write_begin,  	.write_end	= generic_write_end,  	.bmap		= hfsplus_bmap, @@ -153,7 +167,6 @@ const struct address_space_operations hfsplus_btree_aops = {  const struct address_space_operations hfsplus_aops = {  	.readpage	= hfsplus_readpage,  	.writepage	= hfsplus_writepage, -	.sync_page	= block_sync_page,  	.write_begin	= hfsplus_write_begin,  	.write_end	= generic_write_end,  	.bmap		= hfsplus_bmap, @@ -166,72 +179,20 @@ const struct dentry_operations hfsplus_dentry_operations = {  	.d_compare    = hfsplus_compare_dentry,  }; -static struct dentry *hfsplus_file_lookup(struct inode *dir, struct dentry *dentry, -					  struct nameidata *nd) -{ -	struct hfs_find_data fd; -	struct super_block *sb = dir->i_sb; -	struct inode *inode = NULL; -	struct hfsplus_inode_info *hip; -	int err; - -	if (HFSPLUS_IS_RSRC(dir) || strcmp(dentry->d_name.name, "rsrc")) -		goto out; - -	inode = HFSPLUS_I(dir)->rsrc_inode; -	if (inode) -		goto out; - -	inode = new_inode(sb); -	if (!inode) -		return ERR_PTR(-ENOMEM); - -	hip = HFSPLUS_I(inode); -	inode->i_ino = dir->i_ino; -	INIT_LIST_HEAD(&hip->open_dir_list); -	mutex_init(&hip->extents_lock); -	hip->flags = HFSPLUS_FLG_RSRC; - -	hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); -	err = hfsplus_find_cat(sb, dir->i_ino, &fd); -	if (!err) -		err = hfsplus_cat_read_inode(inode, &fd); -	hfs_find_exit(&fd); -	if (err) { -		iput(inode); -		return ERR_PTR(err); -	} -	hip->rsrc_inode = dir; -	HFSPLUS_I(dir)->rsrc_inode = inode; -	igrab(dir); - -	/* -	 * __mark_inode_dirty expects inodes to be hashed.  Since we don't -	 * want resource fork inodes in the regular inode space, we make them -	 * appear hashed, but do not put on any lists.  hlist_del() -	 * will work fine and require no locking. -	 */ -	hlist_add_fake(&inode->i_hash); - -	mark_inode_dirty(inode); -out: -	d_add(dentry, inode); -	return NULL; -} - -static void hfsplus_get_perms(struct inode *inode, struct hfsplus_perm *perms, int dir) +static void hfsplus_get_perms(struct inode *inode, +		struct hfsplus_perm *perms, int dir)  {  	struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);  	u16 mode;  	mode = be16_to_cpu(perms->mode); -	inode->i_uid = be32_to_cpu(perms->owner); -	if (!inode->i_uid && !mode) +	i_uid_write(inode, be32_to_cpu(perms->owner)); +	if (!i_uid_read(inode) && !mode)  		inode->i_uid = sbi->uid; -	inode->i_gid = be32_to_cpu(perms->group); -	if (!inode->i_gid && !mode) +	i_gid_write(inode, be32_to_cpu(perms->group)); +	if (!i_gid_read(inode) && !mode)  		inode->i_gid = sbi->gid;  	if (dir) { @@ -292,56 +253,98 @@ static int hfsplus_setattr(struct dentry *dentry, struct iattr *attr)  	if ((attr->ia_valid & ATTR_SIZE) &&  	    attr->ia_size != i_size_read(inode)) { -		error = vmtruncate(inode, attr->ia_size); -		if (error) -			return error; +		inode_dio_wait(inode); +		truncate_setsize(inode, attr->ia_size); +		hfsplus_file_truncate(inode);  	}  	setattr_copy(inode, attr);  	mark_inode_dirty(inode); + +	if (attr->ia_valid & ATTR_MODE) { +		error = posix_acl_chmod(inode, inode->i_mode); +		if (unlikely(error)) +			return error; +	} +  	return 0;  } -static int hfsplus_file_fsync(struct file *filp, int datasync) +int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end, +		       int datasync)  { -	struct inode *inode = filp->f_mapping->host; -	struct super_block * sb; -	int ret, err; - -	/* sync the inode to buffers */ -	ret = write_inode_now(inode, 0); - -	/* sync the superblock to buffers */ -	sb = inode->i_sb; -	if (sb->s_dirt) { -		if (!(sb->s_flags & MS_RDONLY)) -			hfsplus_sync_fs(sb, 1); -		else -			sb->s_dirt = 0; +	struct inode *inode = file->f_mapping->host; +	struct hfsplus_inode_info *hip = HFSPLUS_I(inode); +	struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb); +	int error = 0, error2; + +	error = filemap_write_and_wait_range(inode->i_mapping, start, end); +	if (error) +		return error; +	mutex_lock(&inode->i_mutex); + +	/* +	 * Sync inode metadata into the catalog and extent trees. +	 */ +	sync_inode_metadata(inode, 1); + +	/* +	 * And explicitly write out the btrees. +	 */ +	if (test_and_clear_bit(HFSPLUS_I_CAT_DIRTY, &hip->flags)) +		error = filemap_write_and_wait(sbi->cat_tree->inode->i_mapping); + +	if (test_and_clear_bit(HFSPLUS_I_EXT_DIRTY, &hip->flags)) { +		error2 = +			filemap_write_and_wait(sbi->ext_tree->inode->i_mapping); +		if (!error) +			error = error2;  	} -	/* .. finally sync the buffers to disk */ -	err = sync_blockdev(sb->s_bdev); -	if (!ret) -		ret = err; -	return ret; +	if (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY, &hip->flags)) { +		if (sbi->attr_tree) { +			error2 = +				filemap_write_and_wait( +					    sbi->attr_tree->inode->i_mapping); +			if (!error) +				error = error2; +		} else { +			pr_err("sync non-existent attributes tree\n"); +		} +	} + +	if (test_and_clear_bit(HFSPLUS_I_ALLOC_DIRTY, &hip->flags)) { +		error2 = filemap_write_and_wait(sbi->alloc_file->i_mapping); +		if (!error) +			error = error2; +	} + +	if (!test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags)) +		blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL, NULL); + +	mutex_unlock(&inode->i_mutex); + +	return error;  }  static const struct inode_operations hfsplus_file_inode_operations = { -	.lookup		= hfsplus_file_lookup, -	.truncate	= hfsplus_file_truncate,  	.setattr	= hfsplus_setattr, -	.setxattr	= hfsplus_setxattr, -	.getxattr	= hfsplus_getxattr, +	.setxattr	= generic_setxattr, +	.getxattr	= generic_getxattr,  	.listxattr	= hfsplus_listxattr, +	.removexattr	= generic_removexattr, +#ifdef CONFIG_HFSPLUS_FS_POSIX_ACL +	.get_acl	= hfsplus_get_posix_acl, +	.set_acl	= hfsplus_set_posix_acl, +#endif  };  static const struct file_operations hfsplus_file_operations = { -	.llseek 	= generic_file_llseek, -	.read		= do_sync_read, -	.aio_read	= generic_file_aio_read, -	.write		= do_sync_write, -	.aio_write	= generic_file_aio_write, +	.llseek		= generic_file_llseek, +	.read		= new_sync_read, +	.read_iter	= generic_file_read_iter, +	.write		= new_sync_write, +	.write_iter	= generic_file_write_iter,  	.mmap		= generic_file_mmap,  	.splice_read	= generic_file_splice_read,  	.fsync		= hfsplus_file_fsync, @@ -350,7 +353,7 @@ static const struct file_operations hfsplus_file_operations = {  	.unlocked_ioctl = hfsplus_ioctl,  }; -struct inode *hfsplus_new_inode(struct super_block *sb, int mode) +struct inode *hfsplus_new_inode(struct super_block *sb, umode_t mode)  {  	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);  	struct inode *inode = new_inode(sb); @@ -363,14 +366,17 @@ struct inode *hfsplus_new_inode(struct super_block *sb, int mode)  	inode->i_mode = mode;  	inode->i_uid = current_fsuid();  	inode->i_gid = current_fsgid(); -	inode->i_nlink = 1; +	set_nlink(inode, 1);  	inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC;  	hip = HFSPLUS_I(inode);  	INIT_LIST_HEAD(&hip->open_dir_list);  	mutex_init(&hip->extents_lock);  	atomic_set(&hip->opencnt, 0); +	hip->extent_state = 0;  	hip->flags = 0; +	hip->userflags = 0; +	hip->subfolders = 0;  	memset(hip->first_extents, 0, sizeof(hfsplus_extent_rec));  	memset(hip->cached_extents, 0, sizeof(hfsplus_extent_rec));  	hip->alloc_blocks = 0; @@ -400,7 +406,7 @@ struct inode *hfsplus_new_inode(struct super_block *sb, int mode)  		sbi->file_count++;  	insert_inode_hash(inode);  	mark_inode_dirty(inode); -	sb->s_dirt = 1; +	hfsplus_mark_mdb_dirty(sb);  	return inode;  } @@ -411,7 +417,7 @@ void hfsplus_delete_inode(struct inode *inode)  	if (S_ISDIR(inode->i_mode)) {  		HFSPLUS_SB(sb)->folder_count--; -		sb->s_dirt = 1; +		hfsplus_mark_mdb_dirty(sb);  		return;  	}  	HFSPLUS_SB(sb)->file_count--; @@ -424,7 +430,7 @@ void hfsplus_delete_inode(struct inode *inode)  		inode->i_size = 0;  		hfsplus_file_truncate(inode);  	} -	sb->s_dirt = 1; +	hfsplus_mark_mdb_dirty(sb);  }  void hfsplus_inode_read_fork(struct inode *inode, struct hfsplus_fork_raw *fork) @@ -457,7 +463,8 @@ void hfsplus_inode_read_fork(struct inode *inode, struct hfsplus_fork_raw *fork)  	}  } -void hfsplus_inode_write_fork(struct inode *inode, struct hfsplus_fork_raw *fork) +void hfsplus_inode_write_fork(struct inode *inode, +		struct hfsplus_fork_raw *fork)  {  	memcpy(&fork->extents, &HFSPLUS_I(inode)->first_extents,  	       sizeof(hfsplus_extent_rec)); @@ -482,13 +489,17 @@ int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd)  		hfs_bnode_read(fd->bnode, &entry, fd->entryoffset,  					sizeof(struct hfsplus_cat_folder));  		hfsplus_get_perms(inode, &folder->permissions, 1); -		inode->i_nlink = 1; +		set_nlink(inode, 1);  		inode->i_size = 2 + be32_to_cpu(folder->valence);  		inode->i_atime = hfsp_mt2ut(folder->access_date);  		inode->i_mtime = hfsp_mt2ut(folder->content_mod_date);  		inode->i_ctime = hfsp_mt2ut(folder->attribute_mod_date);  		HFSPLUS_I(inode)->create_date = folder->create_date;  		HFSPLUS_I(inode)->fs_blocks = 0; +		if (folder->flags & cpu_to_be16(HFSPLUS_HAS_FOLDER_COUNT)) { +			HFSPLUS_I(inode)->subfolders = +				be32_to_cpu(folder->subfolders); +		}  		inode->i_op = &hfsplus_dir_inode_operations;  		inode->i_fop = &hfsplus_dir_operations;  	} else if (type == HFSPLUS_FILE) { @@ -499,13 +510,14 @@ int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd)  		hfs_bnode_read(fd->bnode, &entry, fd->entryoffset,  					sizeof(struct hfsplus_cat_file)); -		hfsplus_inode_read_fork(inode, HFSPLUS_IS_DATA(inode) ? -					&file->data_fork : &file->rsrc_fork); +		hfsplus_inode_read_fork(inode, HFSPLUS_IS_RSRC(inode) ? +					&file->rsrc_fork : &file->data_fork);  		hfsplus_get_perms(inode, &file->permissions, 0); -		inode->i_nlink = 1; +		set_nlink(inode, 1);  		if (S_ISREG(inode->i_mode)) {  			if (file->permissions.dev) -				inode->i_nlink = be32_to_cpu(file->permissions.dev); +				set_nlink(inode, +					  be32_to_cpu(file->permissions.dev));  			inode->i_op = &hfsplus_file_inode_operations;  			inode->i_fop = &hfsplus_file_operations;  			inode->i_mapping->a_ops = &hfsplus_aops; @@ -521,7 +533,7 @@ int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd)  		inode->i_ctime = hfsp_mt2ut(file->attribute_mod_date);  		HFSPLUS_I(inode)->create_date = file->create_date;  	} else { -		printk(KERN_ERR "hfs: bad catalog entry used to create inode\n"); +		pr_err("bad catalog entry used to create inode\n");  		res = -EIO;  	}  	return res; @@ -560,6 +572,10 @@ int hfsplus_cat_write_inode(struct inode *inode)  		folder->content_mod_date = hfsp_ut2mt(inode->i_mtime);  		folder->attribute_mod_date = hfsp_ut2mt(inode->i_ctime);  		folder->valence = cpu_to_be32(inode->i_size - 2); +		if (folder->flags & cpu_to_be16(HFSPLUS_HAS_FOLDER_COUNT)) { +			folder->subfolders = +				cpu_to_be32(HFSPLUS_I(inode)->subfolders); +		}  		hfs_bnode_write(fd.bnode, &entry, fd.entryoffset,  					 sizeof(struct hfsplus_cat_folder));  	} else if (HFSPLUS_IS_RSRC(inode)) { @@ -578,7 +594,9 @@ int hfsplus_cat_write_inode(struct inode *inode)  					sizeof(struct hfsplus_cat_file));  		hfsplus_inode_write_fork(inode, &file->data_fork);  		hfsplus_cat_set_perms(inode, &file->permissions); -		if ((file->permissions.rootflags | file->permissions.userflags) & HFSPLUS_FLG_IMMUTABLE) +		if (HFSPLUS_FLG_IMMUTABLE & +				(file->permissions.rootflags | +					file->permissions.userflags))  			file->flags |= cpu_to_be16(HFSPLUS_FILE_LOCKED);  		else  			file->flags &= cpu_to_be16(~HFSPLUS_FILE_LOCKED); @@ -588,6 +606,8 @@ int hfsplus_cat_write_inode(struct inode *inode)  		hfs_bnode_write(fd.bnode, &entry, fd.entryoffset,  					 sizeof(struct hfsplus_cat_file));  	} + +	set_bit(HFSPLUS_I_CAT_DIRTY, &HFSPLUS_I(inode)->flags);  out:  	hfs_find_exit(&fd);  	return 0; diff --git a/fs/hfsplus/ioctl.c b/fs/hfsplus/ioctl.c index 40a85a3ded6..d3ff5cc317d 100644 --- a/fs/hfsplus/ioctl.c +++ b/fs/hfsplus/ioctl.c @@ -16,19 +16,55 @@  #include <linux/fs.h>  #include <linux/mount.h>  #include <linux/sched.h> -#include <linux/xattr.h>  #include <asm/uaccess.h>  #include "hfsplus_fs.h" +/* + * "Blessing" an HFS+ filesystem writes metadata to the superblock informing + * the platform firmware which file to boot from + */ +static int hfsplus_ioctl_bless(struct file *file, int __user *user_flags) +{ +	struct dentry *dentry = file->f_path.dentry; +	struct inode *inode = dentry->d_inode; +	struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb); +	struct hfsplus_vh *vh = sbi->s_vhdr; +	struct hfsplus_vh *bvh = sbi->s_backup_vhdr; +	u32 cnid = (unsigned long)dentry->d_fsdata; + +	if (!capable(CAP_SYS_ADMIN)) +		return -EPERM; + +	mutex_lock(&sbi->vh_mutex); + +	/* Directory containing the bootable system */ +	vh->finder_info[0] = bvh->finder_info[0] = +		cpu_to_be32(parent_ino(dentry)); + +	/* +	 * Bootloader. Just using the inode here breaks in the case of +	 * hard links - the firmware wants the ID of the hard link file, +	 * but the inode points at the indirect inode +	 */ +	vh->finder_info[1] = bvh->finder_info[1] = cpu_to_be32(cnid); + +	/* Per spec, the OS X system folder - same as finder_info[0] here */ +	vh->finder_info[5] = bvh->finder_info[5] = +		cpu_to_be32(parent_ino(dentry)); + +	mutex_unlock(&sbi->vh_mutex); +	return 0; +} +  static int hfsplus_ioctl_getflags(struct file *file, int __user *user_flags)  { -	struct inode *inode = file->f_path.dentry->d_inode; +	struct inode *inode = file_inode(file);  	struct hfsplus_inode_info *hip = HFSPLUS_I(inode);  	unsigned int flags = 0;  	if (inode->i_flags & S_IMMUTABLE)  		flags |= FS_IMMUTABLE_FL; -	if (inode->i_flags |= S_APPEND) +	if (inode->i_flags & S_APPEND)  		flags |= FS_APPEND_FL;  	if (hip->userflags & HFSPLUS_FLG_NODUMP)  		flags |= FS_NODUMP_FL; @@ -38,16 +74,16 @@ static int hfsplus_ioctl_getflags(struct file *file, int __user *user_flags)  static int hfsplus_ioctl_setflags(struct file *file, int __user *user_flags)  { -	struct inode *inode = file->f_path.dentry->d_inode; +	struct inode *inode = file_inode(file);  	struct hfsplus_inode_info *hip = HFSPLUS_I(inode);  	unsigned int flags;  	int err = 0; -	err = mnt_want_write(file->f_path.mnt); +	err = mnt_want_write_file(file);  	if (err)  		goto out; -	if (!is_owner_or_cap(inode)) { +	if (!inode_owner_or_capable(inode)) {  		err = -EACCES;  		goto out_drop_write;  	} @@ -94,7 +130,7 @@ static int hfsplus_ioctl_setflags(struct file *file, int __user *user_flags)  out_unlock_inode:  	mutex_unlock(&inode->i_mutex);  out_drop_write: -	mnt_drop_write(file->f_path.mnt); +	mnt_drop_write_file(file);  out:  	return err;  } @@ -108,112 +144,9 @@ long hfsplus_ioctl(struct file *file, unsigned int cmd, unsigned long arg)  		return hfsplus_ioctl_getflags(file, argp);  	case HFSPLUS_IOC_EXT2_SETFLAGS:  		return hfsplus_ioctl_setflags(file, argp); +	case HFSPLUS_IOC_BLESS: +		return hfsplus_ioctl_bless(file, argp);  	default:  		return -ENOTTY;  	}  } - -int hfsplus_setxattr(struct dentry *dentry, const char *name, -		     const void *value, size_t size, int flags) -{ -	struct inode *inode = dentry->d_inode; -	struct hfs_find_data fd; -	hfsplus_cat_entry entry; -	struct hfsplus_cat_file *file; -	int res; - -	if (!S_ISREG(inode->i_mode) || HFSPLUS_IS_RSRC(inode)) -		return -EOPNOTSUPP; - -	res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &fd); -	if (res) -		return res; -	res = hfsplus_find_cat(inode->i_sb, inode->i_ino, &fd); -	if (res) -		goto out; -	hfs_bnode_read(fd.bnode, &entry, fd.entryoffset, -			sizeof(struct hfsplus_cat_file)); -	file = &entry.file; - -	if (!strcmp(name, "hfs.type")) { -		if (size == 4) -			memcpy(&file->user_info.fdType, value, 4); -		else -			res = -ERANGE; -	} else if (!strcmp(name, "hfs.creator")) { -		if (size == 4) -			memcpy(&file->user_info.fdCreator, value, 4); -		else -			res = -ERANGE; -	} else -		res = -EOPNOTSUPP; -	if (!res) -		hfs_bnode_write(fd.bnode, &entry, fd.entryoffset, -				sizeof(struct hfsplus_cat_file)); -out: -	hfs_find_exit(&fd); -	return res; -} - -ssize_t hfsplus_getxattr(struct dentry *dentry, const char *name, -			 void *value, size_t size) -{ -	struct inode *inode = dentry->d_inode; -	struct hfs_find_data fd; -	hfsplus_cat_entry entry; -	struct hfsplus_cat_file *file; -	ssize_t res = 0; - -	if (!S_ISREG(inode->i_mode) || HFSPLUS_IS_RSRC(inode)) -		return -EOPNOTSUPP; - -	if (size) { -		res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &fd); -		if (res) -			return res; -		res = hfsplus_find_cat(inode->i_sb, inode->i_ino, &fd); -		if (res) -			goto out; -		hfs_bnode_read(fd.bnode, &entry, fd.entryoffset, -				sizeof(struct hfsplus_cat_file)); -	} -	file = &entry.file; - -	if (!strcmp(name, "hfs.type")) { -		if (size >= 4) { -			memcpy(value, &file->user_info.fdType, 4); -			res = 4; -		} else -			res = size ? -ERANGE : 4; -	} else if (!strcmp(name, "hfs.creator")) { -		if (size >= 4) { -			memcpy(value, &file->user_info.fdCreator, 4); -			res = 4; -		} else -			res = size ? -ERANGE : 4; -	} else -		res = -EOPNOTSUPP; -out: -	if (size) -		hfs_find_exit(&fd); -	return res; -} - -#define HFSPLUS_ATTRLIST_SIZE (sizeof("hfs.creator")+sizeof("hfs.type")) - -ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size) -{ -	struct inode *inode = dentry->d_inode; - -	if (!S_ISREG(inode->i_mode) || HFSPLUS_IS_RSRC(inode)) -		return -EOPNOTSUPP; - -	if (!buffer || !size) -		return HFSPLUS_ATTRLIST_SIZE; -	if (size < HFSPLUS_ATTRLIST_SIZE) -		return -ERANGE; -	strcpy(buffer, "hfs.type"); -	strcpy(buffer + sizeof("hfs.type"), "hfs.creator"); - -	return HFSPLUS_ATTRLIST_SIZE; -} diff --git a/fs/hfsplus/options.c b/fs/hfsplus/options.c index f9ab276a4d8..c90b72ee676 100644 --- a/fs/hfsplus/options.c +++ b/fs/hfsplus/options.c @@ -23,6 +23,7 @@ enum {  	opt_umask, opt_uid, opt_gid,  	opt_part, opt_session, opt_nls,  	opt_nodecompose, opt_decompose, +	opt_barrier, opt_nobarrier,  	opt_force, opt_err  }; @@ -37,6 +38,8 @@ static const match_table_t tokens = {  	{ opt_nls, "nls=%s" },  	{ opt_decompose, "decompose" },  	{ opt_nodecompose, "nodecompose" }, +	{ opt_barrier, "barrier" }, +	{ opt_nobarrier, "nobarrier" },  	{ opt_force, "force" },  	{ opt_err, NULL }  }; @@ -65,6 +68,32 @@ static inline int match_fourchar(substring_t *arg, u32 *result)  	return 0;  } +int hfsplus_parse_options_remount(char *input, int *force) +{ +	char *p; +	substring_t args[MAX_OPT_ARGS]; +	int token; + +	if (!input) +		return 1; + +	while ((p = strsep(&input, ",")) != NULL) { +		if (!*p) +			continue; + +		token = match_token(p, tokens, args); +		switch (token) { +		case opt_force: +			*force = 1; +			break; +		default: +			break; +		} +	} + +	return 1; +} +  /* Parse options from mount. Returns 0 on failure */  /* input is the options passed to mount() as a string */  int hfsplus_parse_options(char *input, struct hfsplus_sb_info *sbi) @@ -84,59 +113,68 @@ int hfsplus_parse_options(char *input, struct hfsplus_sb_info *sbi)  		switch (token) {  		case opt_creator:  			if (match_fourchar(&args[0], &sbi->creator)) { -				printk(KERN_ERR "hfs: creator requires a 4 character value\n"); +				pr_err("creator requires a 4 character value\n");  				return 0;  			}  			break;  		case opt_type:  			if (match_fourchar(&args[0], &sbi->type)) { -				printk(KERN_ERR "hfs: type requires a 4 character value\n"); +				pr_err("type requires a 4 character value\n");  				return 0;  			}  			break;  		case opt_umask:  			if (match_octal(&args[0], &tmp)) { -				printk(KERN_ERR "hfs: umask requires a value\n"); +				pr_err("umask requires a value\n");  				return 0;  			}  			sbi->umask = (umode_t)tmp;  			break;  		case opt_uid:  			if (match_int(&args[0], &tmp)) { -				printk(KERN_ERR "hfs: uid requires an argument\n"); +				pr_err("uid requires an argument\n"); +				return 0; +			} +			sbi->uid = make_kuid(current_user_ns(), (uid_t)tmp); +			if (!uid_valid(sbi->uid)) { +				pr_err("invalid uid specified\n");  				return 0;  			} -			sbi->uid = (uid_t)tmp;  			break;  		case opt_gid:  			if (match_int(&args[0], &tmp)) { -				printk(KERN_ERR "hfs: gid requires an argument\n"); +				pr_err("gid requires an argument\n"); +				return 0; +			} +			sbi->gid = make_kgid(current_user_ns(), (gid_t)tmp); +			if (!gid_valid(sbi->gid)) { +				pr_err("invalid gid specified\n");  				return 0;  			} -			sbi->gid = (gid_t)tmp;  			break;  		case opt_part:  			if (match_int(&args[0], &sbi->part)) { -				printk(KERN_ERR "hfs: part requires an argument\n"); +				pr_err("part requires an argument\n");  				return 0;  			}  			break;  		case opt_session:  			if (match_int(&args[0], &sbi->session)) { -				printk(KERN_ERR "hfs: session requires an argument\n"); +				pr_err("session requires an argument\n");  				return 0;  			}  			break;  		case opt_nls:  			if (sbi->nls) { -				printk(KERN_ERR "hfs: unable to change nls mapping\n"); +				pr_err("unable to change nls mapping\n");  				return 0;  			}  			p = match_strdup(&args[0]);  			if (p)  				sbi->nls = load_nls(p);  			if (!sbi->nls) { -				printk(KERN_ERR "hfs: unable to load nls mapping \"%s\"\n", p); +				pr_err("unable to load nls mapping \"%s\"\n", +				       p);  				kfree(p);  				return 0;  			} @@ -148,6 +186,12 @@ int hfsplus_parse_options(char *input, struct hfsplus_sb_info *sbi)  		case opt_nodecompose:  			set_bit(HFSPLUS_SB_NODECOMPOSE, &sbi->flags);  			break; +		case opt_barrier: +			clear_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags); +			break; +		case opt_nobarrier: +			set_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags); +			break;  		case opt_force:  			set_bit(HFSPLUS_SB_FORCE, &sbi->flags);  			break; @@ -169,15 +213,17 @@ done:  	return 1;  } -int hfsplus_show_options(struct seq_file *seq, struct vfsmount *mnt) +int hfsplus_show_options(struct seq_file *seq, struct dentry *root)  { -	struct hfsplus_sb_info *sbi = HFSPLUS_SB(mnt->mnt_sb); +	struct hfsplus_sb_info *sbi = HFSPLUS_SB(root->d_sb);  	if (sbi->creator != HFSPLUS_DEF_CR_TYPE)  		seq_printf(seq, ",creator=%.4s", (char *)&sbi->creator);  	if (sbi->type != HFSPLUS_DEF_CR_TYPE)  		seq_printf(seq, ",type=%.4s", (char *)&sbi->type); -	seq_printf(seq, ",umask=%o,uid=%u,gid=%u", sbi->umask, sbi->uid, sbi->gid); +	seq_printf(seq, ",umask=%o,uid=%u,gid=%u", sbi->umask, +			from_kuid_munged(&init_user_ns, sbi->uid), +			from_kgid_munged(&init_user_ns, sbi->gid));  	if (sbi->part >= 0)  		seq_printf(seq, ",part=%u", sbi->part);  	if (sbi->session >= 0) @@ -185,6 +231,8 @@ int hfsplus_show_options(struct seq_file *seq, struct vfsmount *mnt)  	if (sbi->nls)  		seq_printf(seq, ",nls=%s", sbi->nls->charset);  	if (test_bit(HFSPLUS_SB_NODECOMPOSE, &sbi->flags)) -		seq_printf(seq, ",nodecompose"); +		seq_puts(seq, ",nodecompose"); +	if (test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags)) +		seq_puts(seq, ",nobarrier");  	return 0;  } diff --git a/fs/hfsplus/part_tbl.c b/fs/hfsplus/part_tbl.c index 208b16c645c..eb355d81e27 100644 --- a/fs/hfsplus/part_tbl.c +++ b/fs/hfsplus/part_tbl.c @@ -2,7 +2,8 @@   * linux/fs/hfsplus/part_tbl.c   *   * Copyright (C) 1996-1997  Paul H. Hargrove - * This file may be distributed under the terms of the GNU General Public License. + * This file may be distributed under the terms of + * the GNU General Public License.   *   * Original code to handle the new style Mac partition table based on   * a patch contributed by Holger Schemel (aeglos@valinor.owl.de). @@ -13,6 +14,7 @@   *   */ +#include <linux/slab.h>  #include "hfsplus_fs.h"  /* offsets to various blocks */ @@ -58,77 +60,98 @@ struct new_pmap {   */  struct old_pmap {  	__be16		pdSig;	/* Signature bytes */ -	struct 	old_pmap_entry { +	struct old_pmap_entry {  		__be32	pdStart;  		__be32	pdSize;  		__be32	pdFSID;  	}	pdEntry[42];  } __packed; +static int hfs_parse_old_pmap(struct super_block *sb, struct old_pmap *pm, +		sector_t *part_start, sector_t *part_size) +{ +	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); +	int i; + +	for (i = 0; i < 42; i++) { +		struct old_pmap_entry *p = &pm->pdEntry[i]; + +		if (p->pdStart && p->pdSize && +		    p->pdFSID == cpu_to_be32(0x54465331)/*"TFS1"*/ && +		    (sbi->part < 0 || sbi->part == i)) { +			*part_start += be32_to_cpu(p->pdStart); +			*part_size = be32_to_cpu(p->pdSize); +			return 0; +		} +	} + +	return -ENOENT; +} + +static int hfs_parse_new_pmap(struct super_block *sb, void *buf, +		struct new_pmap *pm, sector_t *part_start, sector_t *part_size) +{ +	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); +	int size = be32_to_cpu(pm->pmMapBlkCnt); +	int buf_size = hfsplus_min_io_size(sb); +	int res; +	int i = 0; + +	do { +		if (!memcmp(pm->pmPartType, "Apple_HFS", 9) && +		    (sbi->part < 0 || sbi->part == i)) { +			*part_start += be32_to_cpu(pm->pmPyPartStart); +			*part_size = be32_to_cpu(pm->pmPartBlkCnt); +			return 0; +		} + +		if (++i >= size) +			return -ENOENT; + +		pm = (struct new_pmap *)((u8 *)pm + HFSPLUS_SECTOR_SIZE); +		if ((u8 *)pm - (u8 *)buf >= buf_size) { +			res = hfsplus_submit_bio(sb, +						 *part_start + HFS_PMAP_BLK + i, +						 buf, (void **)&pm, READ); +			if (res) +				return res; +		} +	} while (pm->pmSig == cpu_to_be16(HFS_NEW_PMAP_MAGIC)); + +	return -ENOENT; +} +  /* - * hfs_part_find() - * - * Parse the partition map looking for the - * start and length of the 'part'th HFS partition. + * Parse the partition map looking for the start and length of a + * HFS/HFS+ partition.   */  int hfs_part_find(struct super_block *sb, -		  sector_t *part_start, sector_t *part_size) +		sector_t *part_start, sector_t *part_size)  { -	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); -	struct buffer_head *bh; -	__be16 *data; -	int i, size, res; +	void *buf, *data; +	int res; + +	buf = kmalloc(hfsplus_min_io_size(sb), GFP_KERNEL); +	if (!buf) +		return -ENOMEM; -	res = -ENOENT; -	bh = sb_bread512(sb, *part_start + HFS_PMAP_BLK, data); -	if (!bh) -		return -EIO; +	res = hfsplus_submit_bio(sb, *part_start + HFS_PMAP_BLK, +				 buf, &data, READ); +	if (res) +		goto out; -	switch (be16_to_cpu(*data)) { +	switch (be16_to_cpu(*((__be16 *)data))) {  	case HFS_OLD_PMAP_MAGIC: -	  { -		struct old_pmap *pm; -		struct old_pmap_entry *p; - -		pm = (struct old_pmap *)bh->b_data; -		p = pm->pdEntry; -		size = 42; -		for (i = 0; i < size; p++, i++) { -			if (p->pdStart && p->pdSize && -			    p->pdFSID == cpu_to_be32(0x54465331)/*"TFS1"*/ && -			    (sbi->part < 0 || sbi->part == i)) { -				*part_start += be32_to_cpu(p->pdStart); -				*part_size = be32_to_cpu(p->pdSize); -				res = 0; -			} -		} +		res = hfs_parse_old_pmap(sb, data, part_start, part_size);  		break; -	  }  	case HFS_NEW_PMAP_MAGIC: -	  { -		struct new_pmap *pm; - -		pm = (struct new_pmap *)bh->b_data; -		size = be32_to_cpu(pm->pmMapBlkCnt); -		for (i = 0; i < size;) { -			if (!memcmp(pm->pmPartType,"Apple_HFS", 9) && -			    (sbi->part < 0 || sbi->part == i)) { -				*part_start += be32_to_cpu(pm->pmPyPartStart); -				*part_size = be32_to_cpu(pm->pmPartBlkCnt); -				res = 0; -				break; -			} -			brelse(bh); -			bh = sb_bread512(sb, *part_start + HFS_PMAP_BLK + ++i, pm); -			if (!bh) -				return -EIO; -			if (pm->pmSig != cpu_to_be16(HFS_NEW_PMAP_MAGIC)) -				break; -		} +		res = hfs_parse_new_pmap(sb, buf, data, part_start, part_size); +		break; +	default: +		res = -ENOENT;  		break; -	  }  	} -	brelse(bh); - +out: +	kfree(buf);  	return res;  } diff --git a/fs/hfsplus/posix_acl.c b/fs/hfsplus/posix_acl.c new file mode 100644 index 00000000000..df0c9af68d0 --- /dev/null +++ b/fs/hfsplus/posix_acl.c @@ -0,0 +1,140 @@ +/* + * linux/fs/hfsplus/posix_acl.c + * + * Vyacheslav Dubeyko <slava@dubeyko.com> + * + * Handler for Posix Access Control Lists (ACLs) support. + */ + +#include "hfsplus_fs.h" +#include "xattr.h" +#include "acl.h" + +struct posix_acl *hfsplus_get_posix_acl(struct inode *inode, int type) +{ +	struct posix_acl *acl; +	char *xattr_name; +	char *value = NULL; +	ssize_t size; + +	hfs_dbg(ACL_MOD, "[%s]: ino %lu\n", __func__, inode->i_ino); + +	switch (type) { +	case ACL_TYPE_ACCESS: +		xattr_name = POSIX_ACL_XATTR_ACCESS; +		break; +	case ACL_TYPE_DEFAULT: +		xattr_name = POSIX_ACL_XATTR_DEFAULT; +		break; +	default: +		return ERR_PTR(-EINVAL); +	} + +	size = __hfsplus_getxattr(inode, xattr_name, NULL, 0); + +	if (size > 0) { +		value = (char *)hfsplus_alloc_attr_entry(); +		if (unlikely(!value)) +			return ERR_PTR(-ENOMEM); +		size = __hfsplus_getxattr(inode, xattr_name, value, size); +	} + +	if (size > 0) +		acl = posix_acl_from_xattr(&init_user_ns, value, size); +	else if (size == -ENODATA) +		acl = NULL; +	else +		acl = ERR_PTR(size); + +	hfsplus_destroy_attr_entry((hfsplus_attr_entry *)value); + +	if (!IS_ERR(acl)) +		set_cached_acl(inode, type, acl); + +	return acl; +} + +int hfsplus_set_posix_acl(struct inode *inode, struct posix_acl *acl, +		int type) +{ +	int err; +	char *xattr_name; +	size_t size = 0; +	char *value = NULL; + +	hfs_dbg(ACL_MOD, "[%s]: ino %lu\n", __func__, inode->i_ino); + +	switch (type) { +	case ACL_TYPE_ACCESS: +		xattr_name = POSIX_ACL_XATTR_ACCESS; +		if (acl) { +			err = posix_acl_equiv_mode(acl, &inode->i_mode); +			if (err < 0) +				return err; +		} +		err = 0; +		break; + +	case ACL_TYPE_DEFAULT: +		xattr_name = POSIX_ACL_XATTR_DEFAULT; +		if (!S_ISDIR(inode->i_mode)) +			return acl ? -EACCES : 0; +		break; + +	default: +		return -EINVAL; +	} + +	if (acl) { +		size = posix_acl_xattr_size(acl->a_count); +		if (unlikely(size > HFSPLUS_MAX_INLINE_DATA_SIZE)) +			return -ENOMEM; +		value = (char *)hfsplus_alloc_attr_entry(); +		if (unlikely(!value)) +			return -ENOMEM; +		err = posix_acl_to_xattr(&init_user_ns, acl, value, size); +		if (unlikely(err < 0)) +			goto end_set_acl; +	} + +	err = __hfsplus_setxattr(inode, xattr_name, value, size, 0); + +end_set_acl: +	hfsplus_destroy_attr_entry((hfsplus_attr_entry *)value); + +	if (!err) +		set_cached_acl(inode, type, acl); + +	return err; +} + +int hfsplus_init_posix_acl(struct inode *inode, struct inode *dir) +{ +	int err = 0; +	struct posix_acl *default_acl, *acl; + +	hfs_dbg(ACL_MOD, +		"[%s]: ino %lu, dir->ino %lu\n", +		__func__, inode->i_ino, dir->i_ino); + +	if (S_ISLNK(inode->i_mode)) +		return 0; + +	err = posix_acl_create(dir, &inode->i_mode, &default_acl, &acl); +	if (err) +		return err; + +	if (default_acl) { +		err = hfsplus_set_posix_acl(inode, default_acl, +					    ACL_TYPE_DEFAULT); +		posix_acl_release(default_acl); +	} + +	if (acl) { +		if (!err) +			err = hfsplus_set_posix_acl(inode, acl, +						    ACL_TYPE_ACCESS); +		posix_acl_release(acl); +	} +	return err; +} diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c index 52cc746d3ba..4cf2024b87d 100644 --- a/fs/hfsplus/super.c +++ b/fs/hfsplus/super.c @@ -10,6 +10,7 @@  #include <linux/module.h>  #include <linux/init.h>  #include <linux/pagemap.h> +#include <linux/blkdev.h>  #include <linux/fs.h>  #include <linux/slab.h>  #include <linux/vfs.h> @@ -19,6 +20,7 @@ static struct inode *hfsplus_alloc_inode(struct super_block *sb);  static void hfsplus_destroy_inode(struct inode *inode);  #include "hfsplus_fs.h" +#include "xattr.h"  static int hfsplus_system_read_inode(struct inode *inode)  { @@ -66,16 +68,19 @@ struct inode *hfsplus_iget(struct super_block *sb, unsigned long ino)  	INIT_LIST_HEAD(&HFSPLUS_I(inode)->open_dir_list);  	mutex_init(&HFSPLUS_I(inode)->extents_lock);  	HFSPLUS_I(inode)->flags = 0; +	HFSPLUS_I(inode)->extent_state = 0;  	HFSPLUS_I(inode)->rsrc_inode = NULL;  	atomic_set(&HFSPLUS_I(inode)->opencnt, 0);  	if (inode->i_ino >= HFSPLUS_FIRSTUSER_CNID ||  	    inode->i_ino == HFSPLUS_ROOT_CNID) { -		hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &fd); -		err = hfsplus_find_cat(inode->i_sb, inode->i_ino, &fd); -		if (!err) -			err = hfsplus_cat_read_inode(inode, &fd); -		hfs_find_exit(&fd); +		err = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &fd); +		if (!err) { +			err = hfsplus_find_cat(inode->i_sb, inode->i_ino, &fd); +			if (!err) +				err = hfsplus_cat_read_inode(inode, &fd); +			hfs_find_exit(&fd); +		}  	} else {  		err = hfsplus_system_read_inode(inode);  	} @@ -114,26 +119,38 @@ static int hfsplus_system_write_inode(struct inode *inode)  	case HFSPLUS_ATTR_CNID:  		fork = &vhdr->attr_file;  		tree = sbi->attr_tree; +		break;  	default:  		return -EIO;  	}  	if (fork->total_size != cpu_to_be64(inode->i_size)) {  		set_bit(HFSPLUS_SB_WRITEBACKUP, &sbi->flags); -		inode->i_sb->s_dirt = 1; +		hfsplus_mark_mdb_dirty(inode->i_sb);  	}  	hfsplus_inode_write_fork(inode, fork); -	if (tree) -		hfs_btree_write(tree); +	if (tree) { +		int err = hfs_btree_write(tree); + +		if (err) { +			pr_err("b-tree write err: %d, ino %lu\n", +			       err, inode->i_ino); +			return err; +		} +	}  	return 0;  }  static int hfsplus_write_inode(struct inode *inode,  		struct writeback_control *wbc)  { -	dprint(DBG_INODE, "hfsplus_write_inode: %lu\n", inode->i_ino); +	int err; + +	hfs_dbg(INODE, "hfsplus_write_inode: %lu\n", inode->i_ino); -	hfsplus_ext_write_extent(inode); +	err = hfsplus_ext_write_extent(inode); +	if (err) +		return err;  	if (inode->i_ino >= HFSPLUS_FIRSTUSER_CNID ||  	    inode->i_ino == HFSPLUS_ROOT_CNID) @@ -144,94 +161,142 @@ static int hfsplus_write_inode(struct inode *inode,  static void hfsplus_evict_inode(struct inode *inode)  { -	dprint(DBG_INODE, "hfsplus_evict_inode: %lu\n", inode->i_ino); -	truncate_inode_pages(&inode->i_data, 0); -	end_writeback(inode); +	hfs_dbg(INODE, "hfsplus_evict_inode: %lu\n", inode->i_ino); +	truncate_inode_pages_final(&inode->i_data); +	clear_inode(inode);  	if (HFSPLUS_IS_RSRC(inode)) {  		HFSPLUS_I(HFSPLUS_I(inode)->rsrc_inode)->rsrc_inode = NULL;  		iput(HFSPLUS_I(inode)->rsrc_inode);  	}  } -int hfsplus_sync_fs(struct super_block *sb, int wait) +static int hfsplus_sync_fs(struct super_block *sb, int wait)  {  	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);  	struct hfsplus_vh *vhdr = sbi->s_vhdr; +	int write_backup = 0; +	int error, error2; -	dprint(DBG_SUPER, "hfsplus_write_super\n"); +	if (!wait) +		return 0; + +	hfs_dbg(SUPER, "hfsplus_sync_fs\n"); + +	/* +	 * Explicitly write out the special metadata inodes. +	 * +	 * While these special inodes are marked as hashed and written +	 * out peridocically by the flusher threads we redirty them +	 * during writeout of normal inodes, and thus the life lock +	 * prevents us from getting the latest state to disk. +	 */ +	error = filemap_write_and_wait(sbi->cat_tree->inode->i_mapping); +	error2 = filemap_write_and_wait(sbi->ext_tree->inode->i_mapping); +	if (!error) +		error = error2; +	if (sbi->attr_tree) { +		error2 = +		    filemap_write_and_wait(sbi->attr_tree->inode->i_mapping); +		if (!error) +			error = error2; +	} +	error2 = filemap_write_and_wait(sbi->alloc_file->i_mapping); +	if (!error) +		error = error2;  	mutex_lock(&sbi->vh_mutex);  	mutex_lock(&sbi->alloc_mutex); -	sb->s_dirt = 0; -  	vhdr->free_blocks = cpu_to_be32(sbi->free_blocks);  	vhdr->next_cnid = cpu_to_be32(sbi->next_cnid);  	vhdr->folder_count = cpu_to_be32(sbi->folder_count);  	vhdr->file_count = cpu_to_be32(sbi->file_count); -	mark_buffer_dirty(sbi->s_vhbh);  	if (test_and_clear_bit(HFSPLUS_SB_WRITEBACKUP, &sbi->flags)) { -		if (sbi->sect_count) { -			struct buffer_head *bh; -			u32 block, offset; - -			block = sbi->blockoffset; -			block += (sbi->sect_count - 2) >> (sb->s_blocksize_bits - 9); -			offset = ((sbi->sect_count - 2) << 9) & (sb->s_blocksize - 1); -			printk(KERN_DEBUG "hfs: backup: %u,%u,%u,%u\n", -					  sbi->blockoffset, sbi->sect_count, -					  block, offset); -			bh = sb_bread(sb, block); -			if (bh) { -				vhdr = (struct hfsplus_vh *)(bh->b_data + offset); -				if (be16_to_cpu(vhdr->signature) == HFSPLUS_VOLHEAD_SIG) { -					memcpy(vhdr, sbi->s_vhdr, sizeof(*vhdr)); -					mark_buffer_dirty(bh); -					brelse(bh); -				} else -					printk(KERN_WARNING "hfs: backup not found!\n"); -			} -		} +		memcpy(sbi->s_backup_vhdr, sbi->s_vhdr, sizeof(*sbi->s_vhdr)); +		write_backup = 1;  	} + +	error2 = hfsplus_submit_bio(sb, +				   sbi->part_start + HFSPLUS_VOLHEAD_SECTOR, +				   sbi->s_vhdr_buf, NULL, WRITE_SYNC); +	if (!error) +		error = error2; +	if (!write_backup) +		goto out; + +	error2 = hfsplus_submit_bio(sb, +				  sbi->part_start + sbi->sect_count - 2, +				  sbi->s_backup_vhdr_buf, NULL, WRITE_SYNC); +	if (!error) +		error2 = error; +out:  	mutex_unlock(&sbi->alloc_mutex);  	mutex_unlock(&sbi->vh_mutex); -	return 0; + +	if (!test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags)) +		blkdev_issue_flush(sb->s_bdev, GFP_KERNEL, NULL); + +	return error;  } -static void hfsplus_write_super(struct super_block *sb) +static void delayed_sync_fs(struct work_struct *work)  { -	if (!(sb->s_flags & MS_RDONLY)) -		hfsplus_sync_fs(sb, 1); -	else -		sb->s_dirt = 0; +	int err; +	struct hfsplus_sb_info *sbi; + +	sbi = container_of(work, struct hfsplus_sb_info, sync_work.work); + +	spin_lock(&sbi->work_lock); +	sbi->work_queued = 0; +	spin_unlock(&sbi->work_lock); + +	err = hfsplus_sync_fs(sbi->alloc_file->i_sb, 1); +	if (err) +		pr_err("delayed sync fs err %d\n", err); +} + +void hfsplus_mark_mdb_dirty(struct super_block *sb) +{ +	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); +	unsigned long delay; + +	if (sb->s_flags & MS_RDONLY) +		return; + +	spin_lock(&sbi->work_lock); +	if (!sbi->work_queued) { +		delay = msecs_to_jiffies(dirty_writeback_interval * 10); +		queue_delayed_work(system_long_wq, &sbi->sync_work, delay); +		sbi->work_queued = 1; +	} +	spin_unlock(&sbi->work_lock);  }  static void hfsplus_put_super(struct super_block *sb)  {  	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); -	dprint(DBG_SUPER, "hfsplus_put_super\n"); +	hfs_dbg(SUPER, "hfsplus_put_super\n"); -	if (!sb->s_fs_info) -		return; +	cancel_delayed_work_sync(&sbi->sync_work); -	if (sb->s_dirt) -		hfsplus_write_super(sb);  	if (!(sb->s_flags & MS_RDONLY) && sbi->s_vhdr) {  		struct hfsplus_vh *vhdr = sbi->s_vhdr;  		vhdr->modify_date = hfsp_now2mt();  		vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_UNMNT);  		vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_INCNSTNT); -		mark_buffer_dirty(sbi->s_vhbh); -		sync_dirty_buffer(sbi->s_vhbh); + +		hfsplus_sync_fs(sb, 1);  	} +	hfs_btree_close(sbi->attr_tree);  	hfs_btree_close(sbi->cat_tree);  	hfs_btree_close(sbi->ext_tree);  	iput(sbi->alloc_file);  	iput(sbi->hidden_dir); -	brelse(sbi->s_vhbh); +	kfree(sbi->s_vhdr_buf); +	kfree(sbi->s_backup_vhdr_buf);  	unload_nls(sbi->nls);  	kfree(sb->s_fs_info);  	sb->s_fs_info = NULL; @@ -259,30 +324,30 @@ static int hfsplus_statfs(struct dentry *dentry, struct kstatfs *buf)  static int hfsplus_remount(struct super_block *sb, int *flags, char *data)  { +	sync_filesystem(sb);  	if ((*flags & MS_RDONLY) == (sb->s_flags & MS_RDONLY))  		return 0;  	if (!(*flags & MS_RDONLY)) {  		struct hfsplus_vh *vhdr = HFSPLUS_SB(sb)->s_vhdr; -		struct hfsplus_sb_info sbi; +		int force = 0; -		memset(&sbi, 0, sizeof(struct hfsplus_sb_info)); -		sbi.nls = HFSPLUS_SB(sb)->nls; -		if (!hfsplus_parse_options(data, &sbi)) +		if (!hfsplus_parse_options_remount(data, &force))  			return -EINVAL;  		if (!(vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_UNMNT))) { -			printk(KERN_WARNING "hfs: filesystem was not cleanly unmounted, " -			       "running fsck.hfsplus is recommended.  leaving read-only.\n"); +			pr_warn("filesystem was not cleanly unmounted, running fsck.hfsplus is recommended.  leaving read-only.\n");  			sb->s_flags |= MS_RDONLY;  			*flags |= MS_RDONLY; -		} else if (test_bit(HFSPLUS_SB_FORCE, &sbi.flags)) { +		} else if (force) {  			/* nothing */ -		} else if (vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_SOFTLOCK)) { -			printk(KERN_WARNING "hfs: filesystem is marked locked, leaving read-only.\n"); +		} else if (vhdr->attributes & +				cpu_to_be32(HFSPLUS_VOL_SOFTLOCK)) { +			pr_warn("filesystem is marked locked, leaving read-only.\n");  			sb->s_flags |= MS_RDONLY;  			*flags |= MS_RDONLY; -		} else if (vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_JOURNALED)) { -			printk(KERN_WARNING "hfs: filesystem is marked journaled, leaving read-only.\n"); +		} else if (vhdr->attributes & +				cpu_to_be32(HFSPLUS_VOL_JOURNALED)) { +			pr_warn("filesystem is marked journaled, leaving read-only.\n");  			sb->s_flags |= MS_RDONLY;  			*flags |= MS_RDONLY;  		} @@ -296,7 +361,6 @@ static const struct super_operations hfsplus_sops = {  	.write_inode	= hfsplus_write_inode,  	.evict_inode	= hfsplus_evict_inode,  	.put_super	= hfsplus_put_super, -	.write_super	= hfsplus_write_super,  	.sync_fs	= hfsplus_sync_fs,  	.statfs		= hfsplus_statfs,  	.remount_fs	= hfsplus_remount, @@ -312,37 +376,40 @@ static int hfsplus_fill_super(struct super_block *sb, void *data, int silent)  	struct inode *root, *inode;  	struct qstr str;  	struct nls_table *nls = NULL; -	int err = -EINVAL; +	u64 last_fs_block, last_fs_page; +	int err; +	err = -ENOMEM;  	sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);  	if (!sbi) -		return -ENOMEM; +		goto out;  	sb->s_fs_info = sbi;  	mutex_init(&sbi->alloc_mutex);  	mutex_init(&sbi->vh_mutex); +	spin_lock_init(&sbi->work_lock); +	INIT_DELAYED_WORK(&sbi->sync_work, delayed_sync_fs);  	hfsplus_fill_defaults(sbi); + +	err = -EINVAL;  	if (!hfsplus_parse_options(data, sbi)) { -		printk(KERN_ERR "hfs: unable to parse mount options\n"); -		err = -EINVAL; -		goto cleanup; +		pr_err("unable to parse mount options\n"); +		goto out_unload_nls;  	}  	/* temporarily use utf8 to correctly find the hidden dir below */  	nls = sbi->nls;  	sbi->nls = load_nls("utf8");  	if (!sbi->nls) { -		printk(KERN_ERR "hfs: unable to load nls for utf8\n"); -		err = -EINVAL; -		goto cleanup; +		pr_err("unable to load nls for utf8\n"); +		goto out_unload_nls;  	}  	/* Grab the volume header */  	if (hfsplus_read_wrapper(sb)) {  		if (!silent) -			printk(KERN_WARNING "hfs: unable to find HFS+ superblock\n"); -		err = -EINVAL; -		goto cleanup; +			pr_warn("unable to find HFS+ superblock\n"); +		goto out_unload_nls;  	}  	vhdr = sbi->s_vhdr; @@ -350,8 +417,8 @@ static int hfsplus_fill_super(struct super_block *sb, void *data, int silent)  	sb->s_magic = HFSPLUS_VOLHEAD_SIG;  	if (be16_to_cpu(vhdr->version) < HFSPLUS_MIN_VERSION ||  	    be16_to_cpu(vhdr->version) > HFSPLUS_CURRENT_VERSION) { -		printk(KERN_ERR "hfs: wrong filesystem version\n"); -		goto cleanup; +		pr_err("wrong filesystem version\n"); +		goto out_free_vhdr;  	}  	sbi->total_blocks = be32_to_cpu(vhdr->total_blocks);  	sbi->free_blocks = be32_to_cpu(vhdr->free_blocks); @@ -367,110 +434,174 @@ static int hfsplus_fill_super(struct super_block *sb, void *data, int silent)  	if (!sbi->rsrc_clump_blocks)  		sbi->rsrc_clump_blocks = 1; +	err = -EFBIG; +	last_fs_block = sbi->total_blocks - 1; +	last_fs_page = (last_fs_block << sbi->alloc_blksz_shift) >> +			PAGE_CACHE_SHIFT; + +	if ((last_fs_block > (sector_t)(~0ULL) >> (sbi->alloc_blksz_shift - 9)) || +	    (last_fs_page > (pgoff_t)(~0ULL))) { +		pr_err("filesystem size too large\n"); +		goto out_free_vhdr; +	} +  	/* Set up operations so we can load metadata */  	sb->s_op = &hfsplus_sops;  	sb->s_maxbytes = MAX_LFS_FILESIZE;  	if (!(vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_UNMNT))) { -		printk(KERN_WARNING "hfs: Filesystem was not cleanly unmounted, " -		       "running fsck.hfsplus is recommended.  mounting read-only.\n"); +		pr_warn("Filesystem was not cleanly unmounted, running fsck.hfsplus is recommended.  mounting read-only.\n");  		sb->s_flags |= MS_RDONLY;  	} else if (test_and_clear_bit(HFSPLUS_SB_FORCE, &sbi->flags)) {  		/* nothing */  	} else if (vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_SOFTLOCK)) { -		printk(KERN_WARNING "hfs: Filesystem is marked locked, mounting read-only.\n"); +		pr_warn("Filesystem is marked locked, mounting read-only.\n");  		sb->s_flags |= MS_RDONLY; -	} else if ((vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_JOURNALED)) && !(sb->s_flags & MS_RDONLY)) { -		printk(KERN_WARNING "hfs: write access to a journaled filesystem is not supported, " -		       "use the force option at your own risk, mounting read-only.\n"); +	} else if ((vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_JOURNALED)) && +			!(sb->s_flags & MS_RDONLY)) { +		pr_warn("write access to a journaled filesystem is not supported, use the force option at your own risk, mounting read-only.\n");  		sb->s_flags |= MS_RDONLY;  	} +	err = -EINVAL; +  	/* Load metadata objects (B*Trees) */  	sbi->ext_tree = hfs_btree_open(sb, HFSPLUS_EXT_CNID);  	if (!sbi->ext_tree) { -		printk(KERN_ERR "hfs: failed to load extents file\n"); -		goto cleanup; +		pr_err("failed to load extents file\n"); +		goto out_free_vhdr;  	}  	sbi->cat_tree = hfs_btree_open(sb, HFSPLUS_CAT_CNID);  	if (!sbi->cat_tree) { -		printk(KERN_ERR "hfs: failed to load catalog file\n"); -		goto cleanup; +		pr_err("failed to load catalog file\n"); +		goto out_close_ext_tree; +	} +	atomic_set(&sbi->attr_tree_state, HFSPLUS_EMPTY_ATTR_TREE); +	if (vhdr->attr_file.total_blocks != 0) { +		sbi->attr_tree = hfs_btree_open(sb, HFSPLUS_ATTR_CNID); +		if (!sbi->attr_tree) { +			pr_err("failed to load attributes file\n"); +			goto out_close_cat_tree; +		} +		atomic_set(&sbi->attr_tree_state, HFSPLUS_VALID_ATTR_TREE);  	} +	sb->s_xattr = hfsplus_xattr_handlers;  	inode = hfsplus_iget(sb, HFSPLUS_ALLOC_CNID);  	if (IS_ERR(inode)) { -		printk(KERN_ERR "hfs: failed to load allocation file\n"); +		pr_err("failed to load allocation file\n");  		err = PTR_ERR(inode); -		goto cleanup; +		goto out_close_attr_tree;  	}  	sbi->alloc_file = inode;  	/* Load the root directory */  	root = hfsplus_iget(sb, HFSPLUS_ROOT_CNID);  	if (IS_ERR(root)) { -		printk(KERN_ERR "hfs: failed to load root directory\n"); +		pr_err("failed to load root directory\n");  		err = PTR_ERR(root); -		goto cleanup; +		goto out_put_alloc_file;  	} -	sb->s_root = d_alloc_root(root); + +	sb->s_d_op = &hfsplus_dentry_operations; +	sb->s_root = d_make_root(root);  	if (!sb->s_root) { -		iput(root);  		err = -ENOMEM; -		goto cleanup; +		goto out_put_alloc_file;  	} -	sb->s_root->d_op = &hfsplus_dentry_operations;  	str.len = sizeof(HFSP_HIDDENDIR_NAME) - 1;  	str.name = HFSP_HIDDENDIR_NAME; -	hfs_find_init(sbi->cat_tree, &fd); +	err = hfs_find_init(sbi->cat_tree, &fd); +	if (err) +		goto out_put_root;  	hfsplus_cat_build_key(sb, fd.search_key, HFSPLUS_ROOT_CNID, &str);  	if (!hfs_brec_read(&fd, &entry, sizeof(entry))) {  		hfs_find_exit(&fd);  		if (entry.type != cpu_to_be16(HFSPLUS_FOLDER)) -			goto cleanup; +			goto out_put_root;  		inode = hfsplus_iget(sb, be32_to_cpu(entry.folder.id));  		if (IS_ERR(inode)) {  			err = PTR_ERR(inode); -			goto cleanup; +			goto out_put_root;  		}  		sbi->hidden_dir = inode;  	} else  		hfs_find_exit(&fd); -	if (sb->s_flags & MS_RDONLY) -		goto out; +	if (!(sb->s_flags & MS_RDONLY)) { +		/* +		 * H+LX == hfsplusutils, H+Lx == this driver, H+lx is unused +		 * all three are registered with Apple for our use +		 */ +		vhdr->last_mount_vers = cpu_to_be32(HFSP_MOUNT_VERSION); +		vhdr->modify_date = hfsp_now2mt(); +		be32_add_cpu(&vhdr->write_count, 1); +		vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_UNMNT); +		vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_INCNSTNT); +		hfsplus_sync_fs(sb, 1); -	/* H+LX == hfsplusutils, H+Lx == this driver, H+lx is unused -	 * all three are registered with Apple for our use -	 */ -	vhdr->last_mount_vers = cpu_to_be32(HFSP_MOUNT_VERSION); -	vhdr->modify_date = hfsp_now2mt(); -	be32_add_cpu(&vhdr->write_count, 1); -	vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_UNMNT); -	vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_INCNSTNT); -	mark_buffer_dirty(sbi->s_vhbh); -	sync_dirty_buffer(sbi->s_vhbh); - -	if (!sbi->hidden_dir) { -		printk(KERN_DEBUG "hfs: create hidden dir...\n"); - -		mutex_lock(&sbi->vh_mutex); -		sbi->hidden_dir = hfsplus_new_inode(sb, S_IFDIR); -		hfsplus_create_cat(sbi->hidden_dir->i_ino, sb->s_root->d_inode, -				   &str, sbi->hidden_dir); -		mutex_unlock(&sbi->vh_mutex); - -		mark_inode_dirty(sbi->hidden_dir); +		if (!sbi->hidden_dir) { +			mutex_lock(&sbi->vh_mutex); +			sbi->hidden_dir = hfsplus_new_inode(sb, S_IFDIR); +			if (!sbi->hidden_dir) { +				mutex_unlock(&sbi->vh_mutex); +				err = -ENOMEM; +				goto out_put_root; +			} +			err = hfsplus_create_cat(sbi->hidden_dir->i_ino, root, +						 &str, sbi->hidden_dir); +			if (err) { +				mutex_unlock(&sbi->vh_mutex); +				goto out_put_hidden_dir; +			} + +			err = hfsplus_init_inode_security(sbi->hidden_dir, +								root, &str); +			if (err == -EOPNOTSUPP) +				err = 0; /* Operation is not supported. */ +			else if (err) { +				/* +				 * Try to delete anyway without +				 * error analysis. +				 */ +				hfsplus_delete_cat(sbi->hidden_dir->i_ino, +							root, &str); +				mutex_unlock(&sbi->vh_mutex); +				goto out_put_hidden_dir; +			} + +			mutex_unlock(&sbi->vh_mutex); +			hfsplus_mark_inode_dirty(sbi->hidden_dir, +						 HFSPLUS_I_CAT_DIRTY); +		}  	} -out: +  	unload_nls(sbi->nls);  	sbi->nls = nls;  	return 0; -cleanup: -	hfsplus_put_super(sb); +out_put_hidden_dir: +	iput(sbi->hidden_dir); +out_put_root: +	dput(sb->s_root); +	sb->s_root = NULL; +out_put_alloc_file: +	iput(sbi->alloc_file); +out_close_attr_tree: +	hfs_btree_close(sbi->attr_tree); +out_close_cat_tree: +	hfs_btree_close(sbi->cat_tree); +out_close_ext_tree: +	hfs_btree_close(sbi->ext_tree); +out_free_vhdr: +	kfree(sbi->s_vhdr_buf); +	kfree(sbi->s_backup_vhdr_buf); +out_unload_nls: +	unload_nls(sbi->nls);  	unload_nls(nls); +	kfree(sbi); +out:  	return err;  } @@ -488,11 +619,18 @@ static struct inode *hfsplus_alloc_inode(struct super_block *sb)  	return i ? &i->vfs_inode : NULL;  } -static void hfsplus_destroy_inode(struct inode *inode) +static void hfsplus_i_callback(struct rcu_head *head)  { +	struct inode *inode = container_of(head, struct inode, i_rcu); +  	kmem_cache_free(hfsplus_inode_cachep, HFSPLUS_I(inode));  } +static void hfsplus_destroy_inode(struct inode *inode) +{ +	call_rcu(&inode->i_rcu, hfsplus_i_callback); +} +  #define HFSPLUS_INODE_SIZE	sizeof(struct hfsplus_inode_info)  static struct dentry *hfsplus_mount(struct file_system_type *fs_type, @@ -508,6 +646,7 @@ static struct file_system_type hfsplus_fs_type = {  	.kill_sb	= kill_block_super,  	.fs_flags	= FS_REQUIRES_DEV,  }; +MODULE_ALIAS_FS("hfsplus");  static void hfsplus_init_once(void *p)  { @@ -525,15 +664,33 @@ static int __init init_hfsplus_fs(void)  		hfsplus_init_once);  	if (!hfsplus_inode_cachep)  		return -ENOMEM; +	err = hfsplus_create_attr_tree_cache(); +	if (err) +		goto destroy_inode_cache;  	err = register_filesystem(&hfsplus_fs_type);  	if (err) -		kmem_cache_destroy(hfsplus_inode_cachep); +		goto destroy_attr_tree_cache; +	return 0; + +destroy_attr_tree_cache: +	hfsplus_destroy_attr_tree_cache(); + +destroy_inode_cache: +	kmem_cache_destroy(hfsplus_inode_cachep); +  	return err;  }  static void __exit exit_hfsplus_fs(void)  {  	unregister_filesystem(&hfsplus_fs_type); + +	/* +	 * Make sure all delayed rcu free inodes are flushed before we +	 * destroy cache. +	 */ +	rcu_barrier(); +	hfsplus_destroy_attr_tree_cache();  	kmem_cache_destroy(hfsplus_inode_cachep);  } diff --git a/fs/hfsplus/unicode.c b/fs/hfsplus/unicode.c index b66d67de882..e8ef121a4d8 100644 --- a/fs/hfsplus/unicode.c +++ b/fs/hfsplus/unicode.c @@ -17,14 +17,14 @@  /* Returns folded char, or 0 if ignorable */  static inline u16 case_fold(u16 c)  { -        u16 tmp; - -        tmp = hfsplus_case_fold_table[c >> 8]; -        if (tmp) -                tmp = hfsplus_case_fold_table[tmp + (c & 0xff)]; -        else -                tmp = c; -        return tmp; +	u16 tmp; + +	tmp = hfsplus_case_fold_table[c >> 8]; +	if (tmp) +		tmp = hfsplus_case_fold_table[tmp + (c & 0xff)]; +	else +		tmp = c; +	return tmp;  }  /* Compare unicode strings, return values like normal strcmp */ @@ -118,7 +118,9 @@ static u16 *hfsplus_compose_lookup(u16 *p, u16 cc)  	return NULL;  } -int hfsplus_uni2asc(struct super_block *sb, const struct hfsplus_unistr *ustr, char *astr, int *len_p) +int hfsplus_uni2asc(struct super_block *sb, +		const struct hfsplus_unistr *ustr, +		char *astr, int *len_p)  {  	const hfsplus_unichr *ip;  	struct nls_table *nls = HFSPLUS_SB(sb)->nls; @@ -140,7 +142,11 @@ int hfsplus_uni2asc(struct super_block *sb, const struct hfsplus_unistr *ustr, c  		/* search for single decomposed char */  		if (likely(compose))  			ce1 = hfsplus_compose_lookup(hfsplus_compose_table, c0); -		if (ce1 && (cc = ce1[0])) { +		if (ce1) +			cc = ce1[0]; +		else +			cc = 0; +		if (cc) {  			/* start of a possibly decomposed Hangul char */  			if (cc != 0xffff)  				goto done; @@ -171,7 +177,8 @@ int hfsplus_uni2asc(struct super_block *sb, const struct hfsplus_unistr *ustr, c  				goto same;  			c1 = be16_to_cpu(*ip);  			if (likely(compose)) -				ce1 = hfsplus_compose_lookup(hfsplus_compose_table, c1); +				ce1 = hfsplus_compose_lookup( +					hfsplus_compose_table, c1);  			if (ce1)  				break;  			switch (c0) { @@ -199,19 +206,21 @@ int hfsplus_uni2asc(struct super_block *sb, const struct hfsplus_unistr *ustr, c  		if (ce2) {  			i = 1;  			while (i < ustrlen) { -				ce1 = hfsplus_compose_lookup(ce2, be16_to_cpu(ip[i])); +				ce1 = hfsplus_compose_lookup(ce2, +					be16_to_cpu(ip[i]));  				if (!ce1)  					break;  				i++;  				ce2 = ce1;  			} -			if ((cc = ce2[0])) { +			cc = ce2[0]; +			if (cc) {  				ip += i;  				ustrlen -= i;  				goto done;  			}  		} -	same: +same:  		switch (c0) {  		case 0:  			cc = 0x2400; @@ -222,7 +231,7 @@ int hfsplus_uni2asc(struct super_block *sb, const struct hfsplus_unistr *ustr, c  		default:  			cc = c0;  		} -	done: +done:  		res = nls->uni2char(cc, op, len);  		if (res < 0) {  			if (res == -ENAMETOOLONG) @@ -286,7 +295,8 @@ static inline u16 *decompose_unichar(wchar_t uc, int *size)  	return hfsplus_decompose_table + (off / 4);  } -int hfsplus_asc2uni(struct super_block *sb, struct hfsplus_unistr *ustr, +int hfsplus_asc2uni(struct super_block *sb, +		    struct hfsplus_unistr *ustr, int max_unistr_len,  		    const char *astr, int len)  {  	int size, dsize, decompose; @@ -294,11 +304,15 @@ int hfsplus_asc2uni(struct super_block *sb, struct hfsplus_unistr *ustr,  	wchar_t c;  	decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags); -	while (outlen < HFSPLUS_MAX_STRLEN && len > 0) { +	while (outlen < max_unistr_len && len > 0) {  		size = asc2unichar(sb, astr, len, &c); -		if (decompose && (dstr = decompose_unichar(c, &dsize))) { -			if (outlen + dsize > HFSPLUS_MAX_STRLEN) +		if (decompose) +			dstr = decompose_unichar(c, &dsize); +		else +			dstr = NULL; +		if (dstr) { +			if (outlen + dsize > max_unistr_len)  				break;  			do {  				ustr->unicode[outlen++] = cpu_to_be16(*dstr++); @@ -320,7 +334,7 @@ int hfsplus_asc2uni(struct super_block *sb, struct hfsplus_unistr *ustr,   * Composed unicode characters are decomposed and case-folding is performed   * if the appropriate bits are (un)set on the superblock.   */ -int hfsplus_hash_dentry(struct dentry *dentry, struct qstr *str) +int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str)  {  	struct super_block *sb = dentry->d_sb;  	const char *astr; @@ -341,15 +355,23 @@ int hfsplus_hash_dentry(struct dentry *dentry, struct qstr *str)  		astr += size;  		len -= size; -		if (decompose && (dstr = decompose_unichar(c, &dsize))) { +		if (decompose) +			dstr = decompose_unichar(c, &dsize); +		else +			dstr = NULL; +		if (dstr) {  			do {  				c2 = *dstr++; -				if (!casefold || (c2 = case_fold(c2))) +				if (casefold) +					c2 = case_fold(c2); +				if (!casefold || c2)  					hash = partial_name_hash(c2, hash);  			} while (--dsize > 0);  		} else {  			c2 = c; -			if (!casefold || (c2 = case_fold(c2))) +			if (casefold) +				c2 = case_fold(c2); +			if (!casefold || c2)  				hash = partial_name_hash(c2, hash);  		}  	} @@ -363,9 +385,10 @@ int hfsplus_hash_dentry(struct dentry *dentry, struct qstr *str)   * Composed unicode characters are decomposed and case-folding is performed   * if the appropriate bits are (un)set on the superblock.   */ -int hfsplus_compare_dentry(struct dentry *dentry, struct qstr *s1, struct qstr *s2) +int hfsplus_compare_dentry(const struct dentry *parent, const struct dentry *dentry, +		unsigned int len, const char *str, const struct qstr *name)  { -	struct super_block *sb = dentry->d_sb; +	struct super_block *sb = parent->d_sb;  	int casefold, decompose, size;  	int dsize1, dsize2, len1, len2;  	const u16 *dstr1, *dstr2; @@ -375,10 +398,10 @@ int hfsplus_compare_dentry(struct dentry *dentry, struct qstr *s1, struct qstr *  	casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);  	decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags); -	astr1 = s1->name; -	len1 = s1->len; -	astr2 = s2->name; -	len2 = s2->len; +	astr1 = str; +	len1 = len; +	astr2 = name->name; +	len2 = name->len;  	dsize1 = dsize2 = 0;  	dstr1 = dstr2 = NULL; @@ -388,7 +411,9 @@ int hfsplus_compare_dentry(struct dentry *dentry, struct qstr *s1, struct qstr *  			astr1 += size;  			len1 -= size; -			if (!decompose || !(dstr1 = decompose_unichar(c, &dsize1))) { +			if (decompose) +				dstr1 = decompose_unichar(c, &dsize1); +			if (!decompose || !dstr1) {  				c1 = c;  				dstr1 = &c1;  				dsize1 = 1; @@ -400,7 +425,9 @@ int hfsplus_compare_dentry(struct dentry *dentry, struct qstr *s1, struct qstr *  			astr2 += size;  			len2 -= size; -			if (!decompose || !(dstr2 = decompose_unichar(c, &dsize2))) { +			if (decompose) +				dstr2 = decompose_unichar(c, &dsize2); +			if (!decompose || !dstr2) {  				c2 = c;  				dstr2 = &c2;  				dsize2 = 1; @@ -410,12 +437,14 @@ int hfsplus_compare_dentry(struct dentry *dentry, struct qstr *s1, struct qstr *  		c1 = *dstr1;  		c2 = *dstr2;  		if (casefold) { -			if  (!(c1 = case_fold(c1))) { +			c1 = case_fold(c1); +			if (!c1) {  				dstr1++;  				dsize1--;  				continue;  			} -			if (!(c2 = case_fold(c2))) { +			c2 = case_fold(c2); +			if (!c2) {  				dstr2++;  				dsize2--;  				continue; diff --git a/fs/hfsplus/wrapper.c b/fs/hfsplus/wrapper.c index 8972c20b321..cc623567143 100644 --- a/fs/hfsplus/wrapper.c +++ b/fs/hfsplus/wrapper.c @@ -24,6 +24,71 @@ struct hfsplus_wd {  	u16 embed_count;  }; +/** + * hfsplus_submit_bio - Perform block I/O + * @sb: super block of volume for I/O + * @sector: block to read or write, for blocks of HFSPLUS_SECTOR_SIZE bytes + * @buf: buffer for I/O + * @data: output pointer for location of requested data + * @rw: direction of I/O + * + * The unit of I/O is hfsplus_min_io_size(sb), which may be bigger than + * HFSPLUS_SECTOR_SIZE, and @buf must be sized accordingly. On reads + * @data will return a pointer to the start of the requested sector, + * which may not be the same location as @buf. + * + * If @sector is not aligned to the bdev logical block size it will + * be rounded down. For writes this means that @buf should contain data + * that starts at the rounded-down address. As long as the data was + * read using hfsplus_submit_bio() and the same buffer is used things + * will work correctly. + */ +int hfsplus_submit_bio(struct super_block *sb, sector_t sector, +		void *buf, void **data, int rw) +{ +	struct bio *bio; +	int ret = 0; +	u64 io_size; +	loff_t start; +	int offset; + +	/* +	 * Align sector to hardware sector size and find offset. We +	 * assume that io_size is a power of two, which _should_ +	 * be true. +	 */ +	io_size = hfsplus_min_io_size(sb); +	start = (loff_t)sector << HFSPLUS_SECTOR_SHIFT; +	offset = start & (io_size - 1); +	sector &= ~((io_size >> HFSPLUS_SECTOR_SHIFT) - 1); + +	bio = bio_alloc(GFP_NOIO, 1); +	bio->bi_iter.bi_sector = sector; +	bio->bi_bdev = sb->s_bdev; + +	if (!(rw & WRITE) && data) +		*data = (u8 *)buf + offset; + +	while (io_size > 0) { +		unsigned int page_offset = offset_in_page(buf); +		unsigned int len = min_t(unsigned int, PAGE_SIZE - page_offset, +					 io_size); + +		ret = bio_add_page(bio, virt_to_page(buf), len, page_offset); +		if (ret != len) { +			ret = -EIO; +			goto out; +		} +		io_size -= len; +		buf = (u8 *)buf + len; +	} + +	ret = submit_bio_wait(rw, bio); +out: +	bio_put(bio); +	return ret < 0 ? ret : 0; +} +  static int hfsplus_read_mdb(void *bufptr, struct hfsplus_wd *wd)  {  	u32 extent; @@ -40,12 +105,14 @@ static int hfsplus_read_mdb(void *bufptr, struct hfsplus_wd *wd)  	   !(attrib & HFSP_WRAP_ATTRIB_SPARED))  		return 0; -	wd->ablk_size = be32_to_cpu(*(__be32 *)(bufptr + HFSP_WRAPOFF_ABLKSIZE)); +	wd->ablk_size = +		be32_to_cpu(*(__be32 *)(bufptr + HFSP_WRAPOFF_ABLKSIZE));  	if (wd->ablk_size < HFSPLUS_SECTOR_SIZE)  		return 0;  	if (wd->ablk_size % HFSPLUS_SECTOR_SIZE)  		return 0; -	wd->ablk_start = be16_to_cpu(*(__be16 *)(bufptr + HFSP_WRAPOFF_ABLKSTART)); +	wd->ablk_start = +		be16_to_cpu(*(__be16 *)(bufptr + HFSP_WRAPOFF_ABLKSTART));  	extent = get_unaligned_be32(bufptr + HFSP_WRAPOFF_EMBEDEXT);  	wd->embed_start = (extent >> 16) & 0xFFFF; @@ -68,16 +135,18 @@ static int hfsplus_get_last_session(struct super_block *sb,  	if (HFSPLUS_SB(sb)->session >= 0) {  		te.cdte_track = HFSPLUS_SB(sb)->session;  		te.cdte_format = CDROM_LBA; -		res = ioctl_by_bdev(sb->s_bdev, CDROMREADTOCENTRY, (unsigned long)&te); +		res = ioctl_by_bdev(sb->s_bdev, +			CDROMREADTOCENTRY, (unsigned long)&te);  		if (!res && (te.cdte_ctrl & CDROM_DATA_TRACK) == 4) {  			*start = (sector_t)te.cdte_addr.lba << 2;  			return 0;  		} -		printk(KERN_ERR "hfs: invalid session number or type of track\n"); +		pr_err("invalid session number or type of track\n");  		return -EINVAL;  	}  	ms_info.addr_format = CDROM_LBA; -	res = ioctl_by_bdev(sb->s_bdev, CDROMMULTISESSION, (unsigned long)&ms_info); +	res = ioctl_by_bdev(sb->s_bdev, CDROMMULTISESSION, +		(unsigned long)&ms_info);  	if (!res && ms_info.xa_flag)  		*start = (sector_t)ms_info.addr.lba << 2;  	return 0; @@ -88,100 +157,105 @@ static int hfsplus_get_last_session(struct super_block *sb,  int hfsplus_read_wrapper(struct super_block *sb)  {  	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); -	struct buffer_head *bh; -	struct hfsplus_vh *vhdr;  	struct hfsplus_wd wd;  	sector_t part_start, part_size;  	u32 blocksize; +	int error = 0; +	error = -EINVAL;  	blocksize = sb_min_blocksize(sb, HFSPLUS_SECTOR_SIZE);  	if (!blocksize) -		return -EINVAL; +		goto out;  	if (hfsplus_get_last_session(sb, &part_start, &part_size)) -		return -EINVAL; -	if ((u64)part_start + part_size > 0x100000000ULL) { -		pr_err("hfs: volumes larger than 2TB are not supported yet\n"); -		return -EINVAL; -	} -	while (1) { -		bh = sb_bread512(sb, part_start + HFSPLUS_VOLHEAD_SECTOR, vhdr); -		if (!bh) -			return -EIO; - -		if (vhdr->signature == cpu_to_be16(HFSP_WRAP_MAGIC)) { -			if (!hfsplus_read_mdb(vhdr, &wd)) -				goto error; -			wd.ablk_size >>= HFSPLUS_SECTOR_SHIFT; -			part_start += wd.ablk_start + wd.embed_start * wd.ablk_size; -			part_size = wd.embed_count * wd.ablk_size; -			brelse(bh); -			bh = sb_bread512(sb, part_start + HFSPLUS_VOLHEAD_SECTOR, vhdr); -			if (!bh) -				return -EIO; -		} -		if (vhdr->signature == cpu_to_be16(HFSPLUS_VOLHEAD_SIG)) -			break; -		if (vhdr->signature == cpu_to_be16(HFSPLUS_VOLHEAD_SIGX)) { -			set_bit(HFSPLUS_SB_HFSX, &sbi->flags); -			break; -		} -		brelse(bh); +		goto out; + +	error = -ENOMEM; +	sbi->s_vhdr_buf = kmalloc(hfsplus_min_io_size(sb), GFP_KERNEL); +	if (!sbi->s_vhdr_buf) +		goto out; +	sbi->s_backup_vhdr_buf = kmalloc(hfsplus_min_io_size(sb), GFP_KERNEL); +	if (!sbi->s_backup_vhdr_buf) +		goto out_free_vhdr; -		/* check for a partition block +reread: +	error = hfsplus_submit_bio(sb, part_start + HFSPLUS_VOLHEAD_SECTOR, +				   sbi->s_vhdr_buf, (void **)&sbi->s_vhdr, +				   READ); +	if (error) +		goto out_free_backup_vhdr; + +	error = -EINVAL; +	switch (sbi->s_vhdr->signature) { +	case cpu_to_be16(HFSPLUS_VOLHEAD_SIGX): +		set_bit(HFSPLUS_SB_HFSX, &sbi->flags); +		/*FALLTHRU*/ +	case cpu_to_be16(HFSPLUS_VOLHEAD_SIG): +		break; +	case cpu_to_be16(HFSP_WRAP_MAGIC): +		if (!hfsplus_read_mdb(sbi->s_vhdr, &wd)) +			goto out_free_backup_vhdr; +		wd.ablk_size >>= HFSPLUS_SECTOR_SHIFT; +		part_start += (sector_t)wd.ablk_start + +			       (sector_t)wd.embed_start * wd.ablk_size; +		part_size = (sector_t)wd.embed_count * wd.ablk_size; +		goto reread; +	default: +		/* +		 * Check for a partition block. +		 *  		 * (should do this only for cdrom/loop though)  		 */  		if (hfs_part_find(sb, &part_start, &part_size)) -			return -EINVAL; +			goto out_free_backup_vhdr; +		goto reread; +	} + +	error = hfsplus_submit_bio(sb, part_start + part_size - 2, +				   sbi->s_backup_vhdr_buf, +				   (void **)&sbi->s_backup_vhdr, READ); +	if (error) +		goto out_free_backup_vhdr; + +	error = -EINVAL; +	if (sbi->s_backup_vhdr->signature != sbi->s_vhdr->signature) { +		pr_warn("invalid secondary volume header\n"); +		goto out_free_backup_vhdr;  	} -	blocksize = be32_to_cpu(vhdr->blocksize); -	brelse(bh); +	blocksize = be32_to_cpu(sbi->s_vhdr->blocksize); -	/* block size must be at least as large as a sector -	 * and a multiple of 2 +	/* +	 * Block size must be at least as large as a sector and a multiple of 2.  	 */ -	if (blocksize < HFSPLUS_SECTOR_SIZE || -	    ((blocksize - 1) & blocksize)) -		return -EINVAL; +	if (blocksize < HFSPLUS_SECTOR_SIZE || ((blocksize - 1) & blocksize)) +		goto out_free_backup_vhdr;  	sbi->alloc_blksz = blocksize; -	sbi->alloc_blksz_shift = 0; -	while ((blocksize >>= 1) != 0) -		sbi->alloc_blksz_shift++; -	blocksize = min(sbi->alloc_blksz, (u32)PAGE_SIZE); +	sbi->alloc_blksz_shift = ilog2(blocksize); +	blocksize = min_t(u32, sbi->alloc_blksz, PAGE_SIZE); -	/* align block size to block offset */ +	/* +	 * Align block size to block offset. +	 */  	while (part_start & ((blocksize >> HFSPLUS_SECTOR_SHIFT) - 1))  		blocksize >>= 1;  	if (sb_set_blocksize(sb, blocksize) != blocksize) { -		printk(KERN_ERR "hfs: unable to set blocksize to %u!\n", blocksize); -		return -EINVAL; +		pr_err("unable to set blocksize to %u!\n", blocksize); +		goto out_free_backup_vhdr;  	}  	sbi->blockoffset =  		part_start >> (sb->s_blocksize_bits - HFSPLUS_SECTOR_SHIFT); +	sbi->part_start = part_start;  	sbi->sect_count = part_size;  	sbi->fs_shift = sbi->alloc_blksz_shift - sb->s_blocksize_bits; - -	bh = sb_bread512(sb, part_start + HFSPLUS_VOLHEAD_SECTOR, vhdr); -	if (!bh) -		return -EIO; - -	/* should still be the same... */ -	if (test_bit(HFSPLUS_SB_HFSX, &sbi->flags)) { -		if (vhdr->signature != cpu_to_be16(HFSPLUS_VOLHEAD_SIGX)) -			goto error; -	} else { -		if (vhdr->signature != cpu_to_be16(HFSPLUS_VOLHEAD_SIG)) -			goto error; -	} - -	sbi->s_vhbh = bh; -	sbi->s_vhdr = vhdr; -  	return 0; - error: -	brelse(bh); -	return -EINVAL; + +out_free_backup_vhdr: +	kfree(sbi->s_backup_vhdr_buf); +out_free_vhdr: +	kfree(sbi->s_vhdr_buf); +out: +	return error;  } diff --git a/fs/hfsplus/xattr.c b/fs/hfsplus/xattr.c new file mode 100644 index 00000000000..d98094a9f47 --- /dev/null +++ b/fs/hfsplus/xattr.c @@ -0,0 +1,875 @@ +/* + * linux/fs/hfsplus/xattr.c + * + * Vyacheslav Dubeyko <slava@dubeyko.com> + * + * Logic of processing extended attributes + */ + +#include "hfsplus_fs.h" +#include <linux/posix_acl_xattr.h> +#include <linux/nls.h> +#include "xattr.h" +#include "acl.h" + +static int hfsplus_removexattr(struct inode *inode, const char *name); + +const struct xattr_handler *hfsplus_xattr_handlers[] = { +	&hfsplus_xattr_osx_handler, +	&hfsplus_xattr_user_handler, +	&hfsplus_xattr_trusted_handler, +#ifdef CONFIG_HFSPLUS_FS_POSIX_ACL +	&posix_acl_access_xattr_handler, +	&posix_acl_default_xattr_handler, +#endif +	&hfsplus_xattr_security_handler, +	NULL +}; + +static int strcmp_xattr_finder_info(const char *name) +{ +	if (name) { +		return strncmp(name, HFSPLUS_XATTR_FINDER_INFO_NAME, +				sizeof(HFSPLUS_XATTR_FINDER_INFO_NAME)); +	} +	return -1; +} + +static int strcmp_xattr_acl(const char *name) +{ +	if (name) { +		return strncmp(name, HFSPLUS_XATTR_ACL_NAME, +				sizeof(HFSPLUS_XATTR_ACL_NAME)); +	} +	return -1; +} + +static inline int is_known_namespace(const char *name) +{ +	if (strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN) && +	    strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) && +	    strncmp(name, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN) && +	    strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN)) +		return false; + +	return true; +} + +static void hfsplus_init_header_node(struct inode *attr_file, +					u32 clump_size, +					char *buf, u16 node_size) +{ +	struct hfs_bnode_desc *desc; +	struct hfs_btree_header_rec *head; +	u16 offset; +	__be16 *rec_offsets; +	u32 hdr_node_map_rec_bits; +	char *bmp; +	u32 used_nodes; +	u32 used_bmp_bytes; +	u64 tmp; + +	hfs_dbg(ATTR_MOD, "init_hdr_attr_file: clump %u, node_size %u\n", +		clump_size, node_size); + +	/* The end of the node contains list of record offsets */ +	rec_offsets = (__be16 *)(buf + node_size); + +	desc = (struct hfs_bnode_desc *)buf; +	desc->type = HFS_NODE_HEADER; +	desc->num_recs = cpu_to_be16(HFSPLUS_BTREE_HDR_NODE_RECS_COUNT); +	offset = sizeof(struct hfs_bnode_desc); +	*--rec_offsets = cpu_to_be16(offset); + +	head = (struct hfs_btree_header_rec *)(buf + offset); +	head->node_size = cpu_to_be16(node_size); +	tmp = i_size_read(attr_file); +	do_div(tmp, node_size); +	head->node_count = cpu_to_be32(tmp); +	head->free_nodes = cpu_to_be32(be32_to_cpu(head->node_count) - 1); +	head->clump_size = cpu_to_be32(clump_size); +	head->attributes |= cpu_to_be32(HFS_TREE_BIGKEYS | HFS_TREE_VARIDXKEYS); +	head->max_key_len = cpu_to_be16(HFSPLUS_ATTR_KEYLEN - sizeof(u16)); +	offset += sizeof(struct hfs_btree_header_rec); +	*--rec_offsets = cpu_to_be16(offset); +	offset += HFSPLUS_BTREE_HDR_USER_BYTES; +	*--rec_offsets = cpu_to_be16(offset); + +	hdr_node_map_rec_bits = 8 * (node_size - offset - (4 * sizeof(u16))); +	if (be32_to_cpu(head->node_count) > hdr_node_map_rec_bits) { +		u32 map_node_bits; +		u32 map_nodes; + +		desc->next = cpu_to_be32(be32_to_cpu(head->leaf_tail) + 1); +		map_node_bits = 8 * (node_size - sizeof(struct hfs_bnode_desc) - +					(2 * sizeof(u16)) - 2); +		map_nodes = (be32_to_cpu(head->node_count) - +				hdr_node_map_rec_bits + +				(map_node_bits - 1)) / map_node_bits; +		be32_add_cpu(&head->free_nodes, 0 - map_nodes); +	} + +	bmp = buf + offset; +	used_nodes = +		be32_to_cpu(head->node_count) - be32_to_cpu(head->free_nodes); +	used_bmp_bytes = used_nodes / 8; +	if (used_bmp_bytes) { +		memset(bmp, 0xFF, used_bmp_bytes); +		bmp += used_bmp_bytes; +		used_nodes %= 8; +	} +	*bmp = ~(0xFF >> used_nodes); +	offset += hdr_node_map_rec_bits / 8; +	*--rec_offsets = cpu_to_be16(offset); +} + +static int hfsplus_create_attributes_file(struct super_block *sb) +{ +	int err = 0; +	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); +	struct inode *attr_file; +	struct hfsplus_inode_info *hip; +	u32 clump_size; +	u16 node_size = HFSPLUS_ATTR_TREE_NODE_SIZE; +	char *buf; +	int index, written; +	struct address_space *mapping; +	struct page *page; +	int old_state = HFSPLUS_EMPTY_ATTR_TREE; + +	hfs_dbg(ATTR_MOD, "create_attr_file: ino %d\n", HFSPLUS_ATTR_CNID); + +check_attr_tree_state_again: +	switch (atomic_read(&sbi->attr_tree_state)) { +	case HFSPLUS_EMPTY_ATTR_TREE: +		if (old_state != atomic_cmpxchg(&sbi->attr_tree_state, +						old_state, +						HFSPLUS_CREATING_ATTR_TREE)) +			goto check_attr_tree_state_again; +		break; +	case HFSPLUS_CREATING_ATTR_TREE: +		/* +		 * This state means that another thread is in process +		 * of AttributesFile creation. Theoretically, it is +		 * possible to be here. But really __setxattr() method +		 * first of all calls hfs_find_init() for lookup in +		 * B-tree of CatalogFile. This method locks mutex of +		 * CatalogFile's B-tree. As a result, if some thread +		 * is inside AttributedFile creation operation then +		 * another threads will be waiting unlocking of +		 * CatalogFile's B-tree's mutex. However, if code will +		 * change then we will return error code (-EAGAIN) from +		 * here. Really, it means that first try to set of xattr +		 * fails with error but second attempt will have success. +		 */ +		return -EAGAIN; +	case HFSPLUS_VALID_ATTR_TREE: +		return 0; +	case HFSPLUS_FAILED_ATTR_TREE: +		return -EOPNOTSUPP; +	default: +		BUG(); +	} + +	attr_file = hfsplus_iget(sb, HFSPLUS_ATTR_CNID); +	if (IS_ERR(attr_file)) { +		pr_err("failed to load attributes file\n"); +		return PTR_ERR(attr_file); +	} + +	BUG_ON(i_size_read(attr_file) != 0); + +	hip = HFSPLUS_I(attr_file); + +	clump_size = hfsplus_calc_btree_clump_size(sb->s_blocksize, +						    node_size, +						    sbi->sect_count, +						    HFSPLUS_ATTR_CNID); + +	mutex_lock(&hip->extents_lock); +	hip->clump_blocks = clump_size >> sbi->alloc_blksz_shift; +	mutex_unlock(&hip->extents_lock); + +	if (sbi->free_blocks <= (hip->clump_blocks << 1)) { +		err = -ENOSPC; +		goto end_attr_file_creation; +	} + +	while (hip->alloc_blocks < hip->clump_blocks) { +		err = hfsplus_file_extend(attr_file, false); +		if (unlikely(err)) { +			pr_err("failed to extend attributes file\n"); +			goto end_attr_file_creation; +		} +		hip->phys_size = attr_file->i_size = +			(loff_t)hip->alloc_blocks << sbi->alloc_blksz_shift; +		hip->fs_blocks = hip->alloc_blocks << sbi->fs_shift; +		inode_set_bytes(attr_file, attr_file->i_size); +	} + +	buf = kzalloc(node_size, GFP_NOFS); +	if (!buf) { +		pr_err("failed to allocate memory for header node\n"); +		err = -ENOMEM; +		goto end_attr_file_creation; +	} + +	hfsplus_init_header_node(attr_file, clump_size, buf, node_size); + +	mapping = attr_file->i_mapping; + +	index = 0; +	written = 0; +	for (; written < node_size; index++, written += PAGE_CACHE_SIZE) { +		void *kaddr; + +		page = read_mapping_page(mapping, index, NULL); +		if (IS_ERR(page)) { +			err = PTR_ERR(page); +			goto failed_header_node_init; +		} + +		kaddr = kmap_atomic(page); +		memcpy(kaddr, buf + written, +			min_t(size_t, PAGE_CACHE_SIZE, node_size - written)); +		kunmap_atomic(kaddr); + +		set_page_dirty(page); +		page_cache_release(page); +	} + +	hfsplus_mark_inode_dirty(attr_file, HFSPLUS_I_ATTR_DIRTY); + +	sbi->attr_tree = hfs_btree_open(sb, HFSPLUS_ATTR_CNID); +	if (!sbi->attr_tree) +		pr_err("failed to load attributes file\n"); + +failed_header_node_init: +	kfree(buf); + +end_attr_file_creation: +	iput(attr_file); + +	if (!err) +		atomic_set(&sbi->attr_tree_state, HFSPLUS_VALID_ATTR_TREE); +	else if (err == -ENOSPC) +		atomic_set(&sbi->attr_tree_state, HFSPLUS_EMPTY_ATTR_TREE); +	else +		atomic_set(&sbi->attr_tree_state, HFSPLUS_FAILED_ATTR_TREE); + +	return err; +} + +int __hfsplus_setxattr(struct inode *inode, const char *name, +			const void *value, size_t size, int flags) +{ +	int err = 0; +	struct hfs_find_data cat_fd; +	hfsplus_cat_entry entry; +	u16 cat_entry_flags, cat_entry_type; +	u16 folder_finderinfo_len = sizeof(struct DInfo) + +					sizeof(struct DXInfo); +	u16 file_finderinfo_len = sizeof(struct FInfo) + +					sizeof(struct FXInfo); + +	if ((!S_ISREG(inode->i_mode) && +			!S_ISDIR(inode->i_mode)) || +				HFSPLUS_IS_RSRC(inode)) +		return -EOPNOTSUPP; + +	if (value == NULL) +		return hfsplus_removexattr(inode, name); + +	err = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &cat_fd); +	if (err) { +		pr_err("can't init xattr find struct\n"); +		return err; +	} + +	err = hfsplus_find_cat(inode->i_sb, inode->i_ino, &cat_fd); +	if (err) { +		pr_err("catalog searching failed\n"); +		goto end_setxattr; +	} + +	if (!strcmp_xattr_finder_info(name)) { +		if (flags & XATTR_CREATE) { +			pr_err("xattr exists yet\n"); +			err = -EOPNOTSUPP; +			goto end_setxattr; +		} +		hfs_bnode_read(cat_fd.bnode, &entry, cat_fd.entryoffset, +					sizeof(hfsplus_cat_entry)); +		if (be16_to_cpu(entry.type) == HFSPLUS_FOLDER) { +			if (size == folder_finderinfo_len) { +				memcpy(&entry.folder.user_info, value, +						folder_finderinfo_len); +				hfs_bnode_write(cat_fd.bnode, &entry, +					cat_fd.entryoffset, +					sizeof(struct hfsplus_cat_folder)); +				hfsplus_mark_inode_dirty(inode, +						HFSPLUS_I_CAT_DIRTY); +			} else { +				err = -ERANGE; +				goto end_setxattr; +			} +		} else if (be16_to_cpu(entry.type) == HFSPLUS_FILE) { +			if (size == file_finderinfo_len) { +				memcpy(&entry.file.user_info, value, +						file_finderinfo_len); +				hfs_bnode_write(cat_fd.bnode, &entry, +					cat_fd.entryoffset, +					sizeof(struct hfsplus_cat_file)); +				hfsplus_mark_inode_dirty(inode, +						HFSPLUS_I_CAT_DIRTY); +			} else { +				err = -ERANGE; +				goto end_setxattr; +			} +		} else { +			err = -EOPNOTSUPP; +			goto end_setxattr; +		} +		goto end_setxattr; +	} + +	if (!HFSPLUS_SB(inode->i_sb)->attr_tree) { +		err = hfsplus_create_attributes_file(inode->i_sb); +		if (unlikely(err)) +			goto end_setxattr; +	} + +	if (hfsplus_attr_exists(inode, name)) { +		if (flags & XATTR_CREATE) { +			pr_err("xattr exists yet\n"); +			err = -EOPNOTSUPP; +			goto end_setxattr; +		} +		err = hfsplus_delete_attr(inode, name); +		if (err) +			goto end_setxattr; +		err = hfsplus_create_attr(inode, name, value, size); +		if (err) +			goto end_setxattr; +	} else { +		if (flags & XATTR_REPLACE) { +			pr_err("cannot replace xattr\n"); +			err = -EOPNOTSUPP; +			goto end_setxattr; +		} +		err = hfsplus_create_attr(inode, name, value, size); +		if (err) +			goto end_setxattr; +	} + +	cat_entry_type = hfs_bnode_read_u16(cat_fd.bnode, cat_fd.entryoffset); +	if (cat_entry_type == HFSPLUS_FOLDER) { +		cat_entry_flags = hfs_bnode_read_u16(cat_fd.bnode, +				    cat_fd.entryoffset + +				    offsetof(struct hfsplus_cat_folder, flags)); +		cat_entry_flags |= HFSPLUS_XATTR_EXISTS; +		if (!strcmp_xattr_acl(name)) +			cat_entry_flags |= HFSPLUS_ACL_EXISTS; +		hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset + +				offsetof(struct hfsplus_cat_folder, flags), +				cat_entry_flags); +		hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY); +	} else if (cat_entry_type == HFSPLUS_FILE) { +		cat_entry_flags = hfs_bnode_read_u16(cat_fd.bnode, +				    cat_fd.entryoffset + +				    offsetof(struct hfsplus_cat_file, flags)); +		cat_entry_flags |= HFSPLUS_XATTR_EXISTS; +		if (!strcmp_xattr_acl(name)) +			cat_entry_flags |= HFSPLUS_ACL_EXISTS; +		hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset + +				    offsetof(struct hfsplus_cat_file, flags), +				    cat_entry_flags); +		hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY); +	} else { +		pr_err("invalid catalog entry type\n"); +		err = -EIO; +		goto end_setxattr; +	} + +end_setxattr: +	hfs_find_exit(&cat_fd); +	return err; +} + +static int name_len(const char *xattr_name, int xattr_name_len) +{ +	int len = xattr_name_len + 1; + +	if (!is_known_namespace(xattr_name)) +		len += XATTR_MAC_OSX_PREFIX_LEN; + +	return len; +} + +static int copy_name(char *buffer, const char *xattr_name, int name_len) +{ +	int len = name_len; +	int offset = 0; + +	if (!is_known_namespace(xattr_name)) { +		strncpy(buffer, XATTR_MAC_OSX_PREFIX, XATTR_MAC_OSX_PREFIX_LEN); +		offset += XATTR_MAC_OSX_PREFIX_LEN; +		len += XATTR_MAC_OSX_PREFIX_LEN; +	} + +	strncpy(buffer + offset, xattr_name, name_len); +	memset(buffer + offset + name_len, 0, 1); +	len += 1; + +	return len; +} + +static ssize_t hfsplus_getxattr_finder_info(struct inode *inode, +						void *value, size_t size) +{ +	ssize_t res = 0; +	struct hfs_find_data fd; +	u16 entry_type; +	u16 folder_rec_len = sizeof(struct DInfo) + sizeof(struct DXInfo); +	u16 file_rec_len = sizeof(struct FInfo) + sizeof(struct FXInfo); +	u16 record_len = max(folder_rec_len, file_rec_len); +	u8 folder_finder_info[sizeof(struct DInfo) + sizeof(struct DXInfo)]; +	u8 file_finder_info[sizeof(struct FInfo) + sizeof(struct FXInfo)]; + +	if (size >= record_len) { +		res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &fd); +		if (res) { +			pr_err("can't init xattr find struct\n"); +			return res; +		} +		res = hfsplus_find_cat(inode->i_sb, inode->i_ino, &fd); +		if (res) +			goto end_getxattr_finder_info; +		entry_type = hfs_bnode_read_u16(fd.bnode, fd.entryoffset); + +		if (entry_type == HFSPLUS_FOLDER) { +			hfs_bnode_read(fd.bnode, folder_finder_info, +				fd.entryoffset + +				offsetof(struct hfsplus_cat_folder, user_info), +				folder_rec_len); +			memcpy(value, folder_finder_info, folder_rec_len); +			res = folder_rec_len; +		} else if (entry_type == HFSPLUS_FILE) { +			hfs_bnode_read(fd.bnode, file_finder_info, +				fd.entryoffset + +				offsetof(struct hfsplus_cat_file, user_info), +				file_rec_len); +			memcpy(value, file_finder_info, file_rec_len); +			res = file_rec_len; +		} else { +			res = -EOPNOTSUPP; +			goto end_getxattr_finder_info; +		} +	} else +		res = size ? -ERANGE : record_len; + +end_getxattr_finder_info: +	if (size >= record_len) +		hfs_find_exit(&fd); +	return res; +} + +ssize_t __hfsplus_getxattr(struct inode *inode, const char *name, +			 void *value, size_t size) +{ +	struct hfs_find_data fd; +	hfsplus_attr_entry *entry; +	__be32 xattr_record_type; +	u32 record_type; +	u16 record_length = 0; +	ssize_t res = 0; + +	if ((!S_ISREG(inode->i_mode) && +			!S_ISDIR(inode->i_mode)) || +				HFSPLUS_IS_RSRC(inode)) +		return -EOPNOTSUPP; + +	if (!strcmp_xattr_finder_info(name)) +		return hfsplus_getxattr_finder_info(inode, value, size); + +	if (!HFSPLUS_SB(inode->i_sb)->attr_tree) +		return -EOPNOTSUPP; + +	entry = hfsplus_alloc_attr_entry(); +	if (!entry) { +		pr_err("can't allocate xattr entry\n"); +		return -ENOMEM; +	} + +	res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->attr_tree, &fd); +	if (res) { +		pr_err("can't init xattr find struct\n"); +		goto failed_getxattr_init; +	} + +	res = hfsplus_find_attr(inode->i_sb, inode->i_ino, name, &fd); +	if (res) { +		if (res == -ENOENT) +			res = -ENODATA; +		else +			pr_err("xattr searching failed\n"); +		goto out; +	} + +	hfs_bnode_read(fd.bnode, &xattr_record_type, +			fd.entryoffset, sizeof(xattr_record_type)); +	record_type = be32_to_cpu(xattr_record_type); +	if (record_type == HFSPLUS_ATTR_INLINE_DATA) { +		record_length = hfs_bnode_read_u16(fd.bnode, +				fd.entryoffset + +				offsetof(struct hfsplus_attr_inline_data, +				length)); +		if (record_length > HFSPLUS_MAX_INLINE_DATA_SIZE) { +			pr_err("invalid xattr record size\n"); +			res = -EIO; +			goto out; +		} +	} else if (record_type == HFSPLUS_ATTR_FORK_DATA || +			record_type == HFSPLUS_ATTR_EXTENTS) { +		pr_err("only inline data xattr are supported\n"); +		res = -EOPNOTSUPP; +		goto out; +	} else { +		pr_err("invalid xattr record\n"); +		res = -EIO; +		goto out; +	} + +	if (size) { +		hfs_bnode_read(fd.bnode, entry, fd.entryoffset, +				offsetof(struct hfsplus_attr_inline_data, +					raw_bytes) + record_length); +	} + +	if (size >= record_length) { +		memcpy(value, entry->inline_data.raw_bytes, record_length); +		res = record_length; +	} else +		res = size ? -ERANGE : record_length; + +out: +	hfs_find_exit(&fd); + +failed_getxattr_init: +	hfsplus_destroy_attr_entry(entry); +	return res; +} + +static inline int can_list(const char *xattr_name) +{ +	if (!xattr_name) +		return 0; + +	return strncmp(xattr_name, XATTR_TRUSTED_PREFIX, +			XATTR_TRUSTED_PREFIX_LEN) || +				capable(CAP_SYS_ADMIN); +} + +static ssize_t hfsplus_listxattr_finder_info(struct dentry *dentry, +						char *buffer, size_t size) +{ +	ssize_t res = 0; +	struct inode *inode = dentry->d_inode; +	struct hfs_find_data fd; +	u16 entry_type; +	u8 folder_finder_info[sizeof(struct DInfo) + sizeof(struct DXInfo)]; +	u8 file_finder_info[sizeof(struct FInfo) + sizeof(struct FXInfo)]; +	unsigned long len, found_bit; +	int xattr_name_len, symbols_count; + +	res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &fd); +	if (res) { +		pr_err("can't init xattr find struct\n"); +		return res; +	} + +	res = hfsplus_find_cat(inode->i_sb, inode->i_ino, &fd); +	if (res) +		goto end_listxattr_finder_info; + +	entry_type = hfs_bnode_read_u16(fd.bnode, fd.entryoffset); +	if (entry_type == HFSPLUS_FOLDER) { +		len = sizeof(struct DInfo) + sizeof(struct DXInfo); +		hfs_bnode_read(fd.bnode, folder_finder_info, +				fd.entryoffset + +				offsetof(struct hfsplus_cat_folder, user_info), +				len); +		found_bit = find_first_bit((void *)folder_finder_info, len*8); +	} else if (entry_type == HFSPLUS_FILE) { +		len = sizeof(struct FInfo) + sizeof(struct FXInfo); +		hfs_bnode_read(fd.bnode, file_finder_info, +				fd.entryoffset + +				offsetof(struct hfsplus_cat_file, user_info), +				len); +		found_bit = find_first_bit((void *)file_finder_info, len*8); +	} else { +		res = -EOPNOTSUPP; +		goto end_listxattr_finder_info; +	} + +	if (found_bit >= (len*8)) +		res = 0; +	else { +		symbols_count = sizeof(HFSPLUS_XATTR_FINDER_INFO_NAME) - 1; +		xattr_name_len = +			name_len(HFSPLUS_XATTR_FINDER_INFO_NAME, symbols_count); +		if (!buffer || !size) { +			if (can_list(HFSPLUS_XATTR_FINDER_INFO_NAME)) +				res = xattr_name_len; +		} else if (can_list(HFSPLUS_XATTR_FINDER_INFO_NAME)) { +			if (size < xattr_name_len) +				res = -ERANGE; +			else { +				res = copy_name(buffer, +						HFSPLUS_XATTR_FINDER_INFO_NAME, +						symbols_count); +			} +		} +	} + +end_listxattr_finder_info: +	hfs_find_exit(&fd); + +	return res; +} + +ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size) +{ +	ssize_t err; +	ssize_t res = 0; +	struct inode *inode = dentry->d_inode; +	struct hfs_find_data fd; +	u16 key_len = 0; +	struct hfsplus_attr_key attr_key; +	char *strbuf; +	int xattr_name_len; + +	if ((!S_ISREG(inode->i_mode) && +			!S_ISDIR(inode->i_mode)) || +				HFSPLUS_IS_RSRC(inode)) +		return -EOPNOTSUPP; + +	res = hfsplus_listxattr_finder_info(dentry, buffer, size); +	if (res < 0) +		return res; +	else if (!HFSPLUS_SB(inode->i_sb)->attr_tree) +		return (res == 0) ? -EOPNOTSUPP : res; + +	err = hfs_find_init(HFSPLUS_SB(inode->i_sb)->attr_tree, &fd); +	if (err) { +		pr_err("can't init xattr find struct\n"); +		return err; +	} + +	strbuf = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + +			XATTR_MAC_OSX_PREFIX_LEN + 1, GFP_KERNEL); +	if (!strbuf) { +		res = -ENOMEM; +		goto out; +	} + +	err = hfsplus_find_attr(inode->i_sb, inode->i_ino, NULL, &fd); +	if (err) { +		if (err == -ENOENT) { +			if (res == 0) +				res = -ENODATA; +			goto end_listxattr; +		} else { +			res = err; +			goto end_listxattr; +		} +	} + +	for (;;) { +		key_len = hfs_bnode_read_u16(fd.bnode, fd.keyoffset); +		if (key_len == 0 || key_len > fd.tree->max_key_len) { +			pr_err("invalid xattr key length: %d\n", key_len); +			res = -EIO; +			goto end_listxattr; +		} + +		hfs_bnode_read(fd.bnode, &attr_key, +				fd.keyoffset, key_len + sizeof(key_len)); + +		if (be32_to_cpu(attr_key.cnid) != inode->i_ino) +			goto end_listxattr; + +		xattr_name_len = NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN; +		if (hfsplus_uni2asc(inode->i_sb, +			(const struct hfsplus_unistr *)&fd.key->attr.key_name, +					strbuf, &xattr_name_len)) { +			pr_err("unicode conversion failed\n"); +			res = -EIO; +			goto end_listxattr; +		} + +		if (!buffer || !size) { +			if (can_list(strbuf)) +				res += name_len(strbuf, xattr_name_len); +		} else if (can_list(strbuf)) { +			if (size < (res + name_len(strbuf, xattr_name_len))) { +				res = -ERANGE; +				goto end_listxattr; +			} else +				res += copy_name(buffer + res, +						strbuf, xattr_name_len); +		} + +		if (hfs_brec_goto(&fd, 1)) +			goto end_listxattr; +	} + +end_listxattr: +	kfree(strbuf); +out: +	hfs_find_exit(&fd); +	return res; +} + +static int hfsplus_removexattr(struct inode *inode, const char *name) +{ +	int err = 0; +	struct hfs_find_data cat_fd; +	u16 flags; +	u16 cat_entry_type; +	int is_xattr_acl_deleted = 0; +	int is_all_xattrs_deleted = 0; + +	if (!HFSPLUS_SB(inode->i_sb)->attr_tree) +		return -EOPNOTSUPP; + +	if (!strcmp_xattr_finder_info(name)) +		return -EOPNOTSUPP; + +	err = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &cat_fd); +	if (err) { +		pr_err("can't init xattr find struct\n"); +		return err; +	} + +	err = hfsplus_find_cat(inode->i_sb, inode->i_ino, &cat_fd); +	if (err) { +		pr_err("catalog searching failed\n"); +		goto end_removexattr; +	} + +	err = hfsplus_delete_attr(inode, name); +	if (err) +		goto end_removexattr; + +	is_xattr_acl_deleted = !strcmp_xattr_acl(name); +	is_all_xattrs_deleted = !hfsplus_attr_exists(inode, NULL); + +	if (!is_xattr_acl_deleted && !is_all_xattrs_deleted) +		goto end_removexattr; + +	cat_entry_type = hfs_bnode_read_u16(cat_fd.bnode, cat_fd.entryoffset); + +	if (cat_entry_type == HFSPLUS_FOLDER) { +		flags = hfs_bnode_read_u16(cat_fd.bnode, cat_fd.entryoffset + +				offsetof(struct hfsplus_cat_folder, flags)); +		if (is_xattr_acl_deleted) +			flags &= ~HFSPLUS_ACL_EXISTS; +		if (is_all_xattrs_deleted) +			flags &= ~HFSPLUS_XATTR_EXISTS; +		hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset + +				offsetof(struct hfsplus_cat_folder, flags), +				flags); +		hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY); +	} else if (cat_entry_type == HFSPLUS_FILE) { +		flags = hfs_bnode_read_u16(cat_fd.bnode, cat_fd.entryoffset + +				offsetof(struct hfsplus_cat_file, flags)); +		if (is_xattr_acl_deleted) +			flags &= ~HFSPLUS_ACL_EXISTS; +		if (is_all_xattrs_deleted) +			flags &= ~HFSPLUS_XATTR_EXISTS; +		hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset + +				offsetof(struct hfsplus_cat_file, flags), +				flags); +		hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY); +	} else { +		pr_err("invalid catalog entry type\n"); +		err = -EIO; +		goto end_removexattr; +	} + +end_removexattr: +	hfs_find_exit(&cat_fd); +	return err; +} + +static int hfsplus_osx_getxattr(struct dentry *dentry, const char *name, +					void *buffer, size_t size, int type) +{ +	char *xattr_name; +	int res; + +	if (!strcmp(name, "")) +		return -EINVAL; + +	/* +	 * Don't allow retrieving properly prefixed attributes +	 * by prepending them with "osx." +	 */ +	if (is_known_namespace(name)) +		return -EOPNOTSUPP; +	xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN +		+ XATTR_MAC_OSX_PREFIX_LEN + 1, GFP_KERNEL); +	if (!xattr_name) +		return -ENOMEM; +	strcpy(xattr_name, XATTR_MAC_OSX_PREFIX); +	strcpy(xattr_name + XATTR_MAC_OSX_PREFIX_LEN, name); + +	res = hfsplus_getxattr(dentry, xattr_name, buffer, size); +	kfree(xattr_name); +	return res; +} + +static int hfsplus_osx_setxattr(struct dentry *dentry, const char *name, +		const void *buffer, size_t size, int flags, int type) +{ +	char *xattr_name; +	int res; + +	if (!strcmp(name, "")) +		return -EINVAL; + +	/* +	 * Don't allow setting properly prefixed attributes +	 * by prepending them with "osx." +	 */ +	if (is_known_namespace(name)) +		return -EOPNOTSUPP; +	xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN +		+ XATTR_MAC_OSX_PREFIX_LEN + 1, GFP_KERNEL); +	if (!xattr_name) +		return -ENOMEM; +	strcpy(xattr_name, XATTR_MAC_OSX_PREFIX); +	strcpy(xattr_name + XATTR_MAC_OSX_PREFIX_LEN, name); + +	res = hfsplus_setxattr(dentry, xattr_name, buffer, size, flags); +	kfree(xattr_name); +	return res; +} + +static size_t hfsplus_osx_listxattr(struct dentry *dentry, char *list, +		size_t list_size, const char *name, size_t name_len, int type) +{ +	/* +	 * This method is not used. +	 * It is used hfsplus_listxattr() instead of generic_listxattr(). +	 */ +	return -EOPNOTSUPP; +} + +const struct xattr_handler hfsplus_xattr_osx_handler = { +	.prefix	= XATTR_MAC_OSX_PREFIX, +	.list	= hfsplus_osx_listxattr, +	.get	= hfsplus_osx_getxattr, +	.set	= hfsplus_osx_setxattr, +}; diff --git a/fs/hfsplus/xattr.h b/fs/hfsplus/xattr.h new file mode 100644 index 00000000000..288530cf80b --- /dev/null +++ b/fs/hfsplus/xattr.h @@ -0,0 +1,49 @@ +/* + * linux/fs/hfsplus/xattr.h + * + * Vyacheslav Dubeyko <slava@dubeyko.com> + * + * Logic of processing extended attributes + */ + +#ifndef _LINUX_HFSPLUS_XATTR_H +#define _LINUX_HFSPLUS_XATTR_H + +#include <linux/xattr.h> + +extern const struct xattr_handler hfsplus_xattr_osx_handler; +extern const struct xattr_handler hfsplus_xattr_user_handler; +extern const struct xattr_handler hfsplus_xattr_trusted_handler; +extern const struct xattr_handler hfsplus_xattr_security_handler; + +extern const struct xattr_handler *hfsplus_xattr_handlers[]; + +int __hfsplus_setxattr(struct inode *inode, const char *name, +			const void *value, size_t size, int flags); + +static inline int hfsplus_setxattr(struct dentry *dentry, const char *name, +			const void *value, size_t size, int flags) +{ +	return __hfsplus_setxattr(dentry->d_inode, name, value, size, flags); +} + +ssize_t __hfsplus_getxattr(struct inode *inode, const char *name, +			void *value, size_t size); + +static inline ssize_t hfsplus_getxattr(struct dentry *dentry, +					const char *name, +					void *value, +					size_t size) +{ +	return __hfsplus_getxattr(dentry->d_inode, name, value, size); +} + +ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size); + +int hfsplus_init_security(struct inode *inode, struct inode *dir, +				const struct qstr *qstr); + +int hfsplus_init_inode_security(struct inode *inode, struct inode *dir, +				const struct qstr *qstr); + +#endif diff --git a/fs/hfsplus/xattr_security.c b/fs/hfsplus/xattr_security.c new file mode 100644 index 00000000000..6ec5e107691 --- /dev/null +++ b/fs/hfsplus/xattr_security.c @@ -0,0 +1,124 @@ +/* + * linux/fs/hfsplus/xattr_trusted.c + * + * Vyacheslav Dubeyko <slava@dubeyko.com> + * + * Handler for storing security labels as extended attributes. + */ + +#include <linux/security.h> +#include <linux/nls.h> + +#include "hfsplus_fs.h" +#include "xattr.h" +#include "acl.h" + +static int hfsplus_security_getxattr(struct dentry *dentry, const char *name, +					void *buffer, size_t size, int type) +{ +	char *xattr_name; +	int res; + +	if (!strcmp(name, "")) +		return -EINVAL; + +	xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1, +		GFP_KERNEL); +	if (!xattr_name) +		return -ENOMEM; +	strcpy(xattr_name, XATTR_SECURITY_PREFIX); +	strcpy(xattr_name + XATTR_SECURITY_PREFIX_LEN, name); + +	res = hfsplus_getxattr(dentry, xattr_name, buffer, size); +	kfree(xattr_name); +	return res; +} + +static int hfsplus_security_setxattr(struct dentry *dentry, const char *name, +		const void *buffer, size_t size, int flags, int type) +{ +	char *xattr_name; +	int res; + +	if (!strcmp(name, "")) +		return -EINVAL; + +	xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1, +		GFP_KERNEL); +	if (!xattr_name) +		return -ENOMEM; +	strcpy(xattr_name, XATTR_SECURITY_PREFIX); +	strcpy(xattr_name + XATTR_SECURITY_PREFIX_LEN, name); + +	res = hfsplus_setxattr(dentry, xattr_name, buffer, size, flags); +	kfree(xattr_name); +	return res; +} + +static size_t hfsplus_security_listxattr(struct dentry *dentry, char *list, +		size_t list_size, const char *name, size_t name_len, int type) +{ +	/* +	 * This method is not used. +	 * It is used hfsplus_listxattr() instead of generic_listxattr(). +	 */ +	return -EOPNOTSUPP; +} + +static int hfsplus_initxattrs(struct inode *inode, +				const struct xattr *xattr_array, +				void *fs_info) +{ +	const struct xattr *xattr; +	char *xattr_name; +	int err = 0; + +	xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1, +		GFP_KERNEL); +	if (!xattr_name) +		return -ENOMEM; +	for (xattr = xattr_array; xattr->name != NULL; xattr++) { + +		if (!strcmp(xattr->name, "")) +			continue; + +		strcpy(xattr_name, XATTR_SECURITY_PREFIX); +		strcpy(xattr_name + +			XATTR_SECURITY_PREFIX_LEN, xattr->name); +		memset(xattr_name + +			XATTR_SECURITY_PREFIX_LEN + strlen(xattr->name), 0, 1); + +		err = __hfsplus_setxattr(inode, xattr_name, +					xattr->value, xattr->value_len, 0); +		if (err) +			break; +	} +	kfree(xattr_name); +	return err; +} + +int hfsplus_init_security(struct inode *inode, struct inode *dir, +				const struct qstr *qstr) +{ +	return security_inode_init_security(inode, dir, qstr, +					&hfsplus_initxattrs, NULL); +} + +int hfsplus_init_inode_security(struct inode *inode, +						struct inode *dir, +						const struct qstr *qstr) +{ +	int err; + +	err = hfsplus_init_posix_acl(inode, dir); +	if (!err) +		err = hfsplus_init_security(inode, dir, qstr); +	return err; +} + +const struct xattr_handler hfsplus_xattr_security_handler = { +	.prefix	= XATTR_SECURITY_PREFIX, +	.list	= hfsplus_security_listxattr, +	.get	= hfsplus_security_getxattr, +	.set	= hfsplus_security_setxattr, +}; diff --git a/fs/hfsplus/xattr_trusted.c b/fs/hfsplus/xattr_trusted.c new file mode 100644 index 00000000000..3c5f27e4746 --- /dev/null +++ b/fs/hfsplus/xattr_trusted.c @@ -0,0 +1,71 @@ +/* + * linux/fs/hfsplus/xattr_trusted.c + * + * Vyacheslav Dubeyko <slava@dubeyko.com> + * + * Handler for trusted extended attributes. + */ + +#include <linux/nls.h> + +#include "hfsplus_fs.h" +#include "xattr.h" + +static int hfsplus_trusted_getxattr(struct dentry *dentry, const char *name, +					void *buffer, size_t size, int type) +{ +	char *xattr_name; +	int res; + +	if (!strcmp(name, "")) +		return -EINVAL; + +	xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1, +		GFP_KERNEL); +	if (!xattr_name) +		return -ENOMEM; +	strcpy(xattr_name, XATTR_TRUSTED_PREFIX); +	strcpy(xattr_name + XATTR_TRUSTED_PREFIX_LEN, name); + +	res = hfsplus_getxattr(dentry, xattr_name, buffer, size); +	kfree(xattr_name); +	return res; +} + +static int hfsplus_trusted_setxattr(struct dentry *dentry, const char *name, +		const void *buffer, size_t size, int flags, int type) +{ +	char *xattr_name; +	int res; + +	if (!strcmp(name, "")) +		return -EINVAL; + +	xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1, +		GFP_KERNEL); +	if (!xattr_name) +		return -ENOMEM; +	strcpy(xattr_name, XATTR_TRUSTED_PREFIX); +	strcpy(xattr_name + XATTR_TRUSTED_PREFIX_LEN, name); + +	res = hfsplus_setxattr(dentry, xattr_name, buffer, size, flags); +	kfree(xattr_name); +	return res; +} + +static size_t hfsplus_trusted_listxattr(struct dentry *dentry, char *list, +		size_t list_size, const char *name, size_t name_len, int type) +{ +	/* +	 * This method is not used. +	 * It is used hfsplus_listxattr() instead of generic_listxattr(). +	 */ +	return -EOPNOTSUPP; +} + +const struct xattr_handler hfsplus_xattr_trusted_handler = { +	.prefix	= XATTR_TRUSTED_PREFIX, +	.list	= hfsplus_trusted_listxattr, +	.get	= hfsplus_trusted_getxattr, +	.set	= hfsplus_trusted_setxattr, +}; diff --git a/fs/hfsplus/xattr_user.c b/fs/hfsplus/xattr_user.c new file mode 100644 index 00000000000..2b625a538b6 --- /dev/null +++ b/fs/hfsplus/xattr_user.c @@ -0,0 +1,71 @@ +/* + * linux/fs/hfsplus/xattr_user.c + * + * Vyacheslav Dubeyko <slava@dubeyko.com> + * + * Handler for user extended attributes. + */ + +#include <linux/nls.h> + +#include "hfsplus_fs.h" +#include "xattr.h" + +static int hfsplus_user_getxattr(struct dentry *dentry, const char *name, +					void *buffer, size_t size, int type) +{ +	char *xattr_name; +	int res; + +	if (!strcmp(name, "")) +		return -EINVAL; + +	xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1, +		GFP_KERNEL); +	if (!xattr_name) +		return -ENOMEM; +	strcpy(xattr_name, XATTR_USER_PREFIX); +	strcpy(xattr_name + XATTR_USER_PREFIX_LEN, name); + +	res = hfsplus_getxattr(dentry, xattr_name, buffer, size); +	kfree(xattr_name); +	return res; +} + +static int hfsplus_user_setxattr(struct dentry *dentry, const char *name, +		const void *buffer, size_t size, int flags, int type) +{ +	char *xattr_name; +	int res; + +	if (!strcmp(name, "")) +		return -EINVAL; + +	xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1, +		GFP_KERNEL); +	if (!xattr_name) +		return -ENOMEM; +	strcpy(xattr_name, XATTR_USER_PREFIX); +	strcpy(xattr_name + XATTR_USER_PREFIX_LEN, name); + +	res = hfsplus_setxattr(dentry, xattr_name, buffer, size, flags); +	kfree(xattr_name); +	return res; +} + +static size_t hfsplus_user_listxattr(struct dentry *dentry, char *list, +		size_t list_size, const char *name, size_t name_len, int type) +{ +	/* +	 * This method is not used. +	 * It is used hfsplus_listxattr() instead of generic_listxattr(). +	 */ +	return -EOPNOTSUPP; +} + +const struct xattr_handler hfsplus_xattr_user_handler = { +	.prefix	= XATTR_USER_PREFIX, +	.list	= hfsplus_user_listxattr, +	.get	= hfsplus_user_getxattr, +	.set	= hfsplus_user_setxattr, +};  | 
