mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 09:08:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
31
fs/hfsplus/Kconfig
Normal file
31
fs/hfsplus/Kconfig
Normal file
|
@ -0,0 +1,31 @@
|
|||
config HFSPLUS_FS
|
||||
tristate "Apple Extended HFS file system support"
|
||||
depends on BLOCK
|
||||
select NLS
|
||||
select NLS_UTF8
|
||||
help
|
||||
If you say Y here, you will be able to mount extended format
|
||||
Macintosh-formatted hard drive partitions with full read-write access.
|
||||
|
||||
This file system is often called HFS+ and was introduced with
|
||||
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
|
11
fs/hfsplus/Makefile
Normal file
11
fs/hfsplus/Makefile
Normal file
|
@ -0,0 +1,11 @@
|
|||
#
|
||||
## Makefile for the linux hfsplus filesystem routines.
|
||||
#
|
||||
|
||||
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 \
|
||||
attributes.o xattr.o xattr_user.o xattr_security.o xattr_trusted.o
|
||||
|
||||
hfsplus-$(CONFIG_HFSPLUS_FS_POSIX_ACL) += posix_acl.o
|
27
fs/hfsplus/acl.h
Normal file
27
fs/hfsplus/acl.h
Normal file
|
@ -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 */
|
371
fs/hfsplus/attributes.c
Normal file
371
fs/hfsplus/attributes.c
Normal file
|
@ -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;
|
||||
}
|
295
fs/hfsplus/bfind.c
Normal file
295
fs/hfsplus/bfind.c
Normal file
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/bfind.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* Search routines for btrees
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include "hfsplus_fs.h"
|
||||
|
||||
int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd)
|
||||
{
|
||||
void *ptr;
|
||||
|
||||
fd->tree = tree;
|
||||
fd->bnode = NULL;
|
||||
ptr = kmalloc(tree->max_key_len * 2 + 4, GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return -ENOMEM;
|
||||
fd->search_key = ptr;
|
||||
fd->key = ptr + tree->max_key_len + 2;
|
||||
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;
|
||||
}
|
||||
|
||||
void hfs_find_exit(struct hfs_find_data *fd)
|
||||
{
|
||||
hfs_bnode_put(fd->bnode);
|
||||
kfree(fd->search_key);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
do {
|
||||
rec = (e + b) / 2;
|
||||
len = hfs_brec_lenoff(bnode, rec, &off);
|
||||
keylen = hfs_brec_keylen(bnode, rec);
|
||||
if (keylen == 0) {
|
||||
res = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
hfs_bnode_read(bnode, fd->key, off, keylen);
|
||||
if (rec_found(bnode, fd, &b, &e, &rec)) {
|
||||
res = 0;
|
||||
goto done;
|
||||
}
|
||||
} while (b <= e);
|
||||
|
||||
if (rec != e && e >= 0) {
|
||||
len = hfs_brec_lenoff(bnode, e, &off);
|
||||
keylen = hfs_brec_keylen(bnode, e);
|
||||
if (keylen == 0) {
|
||||
res = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
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, search_strategy_t do_key_compare)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
struct hfs_bnode *bnode;
|
||||
u32 nidx, parent;
|
||||
__be32 data;
|
||||
int height, res;
|
||||
|
||||
tree = fd->tree;
|
||||
if (fd->bnode)
|
||||
hfs_bnode_put(fd->bnode);
|
||||
fd->bnode = NULL;
|
||||
nidx = tree->root;
|
||||
if (!nidx)
|
||||
return -ENOENT;
|
||||
height = tree->depth;
|
||||
res = 0;
|
||||
parent = 0;
|
||||
for (;;) {
|
||||
bnode = hfs_bnode_find(tree, nidx);
|
||||
if (IS_ERR(bnode)) {
|
||||
res = PTR_ERR(bnode);
|
||||
bnode = NULL;
|
||||
break;
|
||||
}
|
||||
if (bnode->height != height)
|
||||
goto invalid;
|
||||
if (bnode->type != (--height ? HFS_NODE_INDEX : HFS_NODE_LEAF))
|
||||
goto invalid;
|
||||
bnode->parent = parent;
|
||||
|
||||
res = __hfs_brec_find(bnode, fd, do_key_compare);
|
||||
if (!height)
|
||||
break;
|
||||
if (fd->record < 0)
|
||||
goto release;
|
||||
|
||||
parent = nidx;
|
||||
hfs_bnode_read(bnode, &data, fd->entryoffset, 4);
|
||||
nidx = be32_to_cpu(data);
|
||||
hfs_bnode_put(bnode);
|
||||
}
|
||||
fd->bnode = bnode;
|
||||
return res;
|
||||
|
||||
invalid:
|
||||
pr_err("inconsistency in B*Tree (%d,%d,%d,%u,%u)\n",
|
||||
height, bnode->height, bnode->type, nidx, parent);
|
||||
res = -EIO;
|
||||
release:
|
||||
hfs_bnode_put(bnode);
|
||||
return res;
|
||||
}
|
||||
|
||||
int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = hfs_brec_find(fd, hfs_find_rec_by_key);
|
||||
if (res)
|
||||
return res;
|
||||
if (fd->entrylength > rec_len)
|
||||
return -EINVAL;
|
||||
hfs_bnode_read(fd->bnode, rec, fd->entryoffset, fd->entrylength);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hfs_brec_goto(struct hfs_find_data *fd, int cnt)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
struct hfs_bnode *bnode;
|
||||
int idx, res = 0;
|
||||
u16 off, len, keylen;
|
||||
|
||||
bnode = fd->bnode;
|
||||
tree = bnode->tree;
|
||||
|
||||
if (cnt < 0) {
|
||||
cnt = -cnt;
|
||||
while (cnt > fd->record) {
|
||||
cnt -= fd->record + 1;
|
||||
fd->record = bnode->num_recs - 1;
|
||||
idx = bnode->prev;
|
||||
if (!idx) {
|
||||
res = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
hfs_bnode_put(bnode);
|
||||
bnode = hfs_bnode_find(tree, idx);
|
||||
if (IS_ERR(bnode)) {
|
||||
res = PTR_ERR(bnode);
|
||||
bnode = NULL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
fd->record -= cnt;
|
||||
} else {
|
||||
while (cnt >= bnode->num_recs - fd->record) {
|
||||
cnt -= bnode->num_recs - fd->record;
|
||||
fd->record = 0;
|
||||
idx = bnode->next;
|
||||
if (!idx) {
|
||||
res = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
hfs_bnode_put(bnode);
|
||||
bnode = hfs_bnode_find(tree, idx);
|
||||
if (IS_ERR(bnode)) {
|
||||
res = PTR_ERR(bnode);
|
||||
bnode = NULL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
fd->record += cnt;
|
||||
}
|
||||
|
||||
len = hfs_brec_lenoff(bnode, fd->record, &off);
|
||||
keylen = hfs_brec_keylen(bnode, fd->record);
|
||||
if (keylen == 0) {
|
||||
res = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
fd->keyoffset = off;
|
||||
fd->keylength = keylen;
|
||||
fd->entryoffset = off + keylen;
|
||||
fd->entrylength = len - keylen;
|
||||
hfs_bnode_read(bnode, fd->key, off, keylen);
|
||||
out:
|
||||
fd->bnode = bnode;
|
||||
return res;
|
||||
}
|
245
fs/hfsplus/bitmap.c
Normal file
245
fs/hfsplus/bitmap.c
Normal file
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/bitmap.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* Handling of allocation file
|
||||
*/
|
||||
|
||||
#include <linux/pagemap.h>
|
||||
|
||||
#include "hfsplus_fs.h"
|
||||
#include "hfsplus_raw.h"
|
||||
|
||||
#define PAGE_CACHE_BITS (PAGE_CACHE_SIZE * 8)
|
||||
|
||||
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;
|
||||
struct address_space *mapping;
|
||||
__be32 *pptr, *curr, *end;
|
||||
u32 mask, start, len, n;
|
||||
__be32 val;
|
||||
int i;
|
||||
|
||||
len = *max;
|
||||
if (!len)
|
||||
return size;
|
||||
|
||||
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);
|
||||
if (IS_ERR(page)) {
|
||||
start = size;
|
||||
goto out;
|
||||
}
|
||||
pptr = kmap(page);
|
||||
curr = pptr + (offset & (PAGE_CACHE_BITS - 1)) / 32;
|
||||
i = offset % 32;
|
||||
offset &= ~(PAGE_CACHE_BITS - 1);
|
||||
if ((size ^ offset) / PAGE_CACHE_BITS)
|
||||
end = pptr + PAGE_CACHE_BITS / 32;
|
||||
else
|
||||
end = pptr + ((size + 31) & (PAGE_CACHE_BITS - 1)) / 32;
|
||||
|
||||
/* scan the first partial u32 for zero bits */
|
||||
val = *curr;
|
||||
if (~val) {
|
||||
n = be32_to_cpu(val);
|
||||
mask = (1U << 31) >> i;
|
||||
for (; i < 32; mask >>= 1, i++) {
|
||||
if (!(n & mask))
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
curr++;
|
||||
|
||||
/* scan complete u32s for the first zero bit */
|
||||
while (1) {
|
||||
while (curr < end) {
|
||||
val = *curr;
|
||||
if (~val) {
|
||||
n = be32_to_cpu(val);
|
||||
mask = 1 << 31;
|
||||
for (i = 0; i < 32; mask >>= 1, i++) {
|
||||
if (!(n & mask))
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
curr++;
|
||||
}
|
||||
kunmap(page);
|
||||
offset += PAGE_CACHE_BITS;
|
||||
if (offset >= size)
|
||||
break;
|
||||
page = read_mapping_page(mapping, offset / PAGE_CACHE_BITS,
|
||||
NULL);
|
||||
if (IS_ERR(page)) {
|
||||
start = size;
|
||||
goto out;
|
||||
}
|
||||
curr = pptr = kmap(page);
|
||||
if ((size ^ offset) / PAGE_CACHE_BITS)
|
||||
end = pptr + PAGE_CACHE_BITS / 32;
|
||||
else
|
||||
end = pptr + ((size + 31) & (PAGE_CACHE_BITS - 1)) / 32;
|
||||
}
|
||||
hfs_dbg(BITMAP, "bitmap full\n");
|
||||
start = size;
|
||||
goto out;
|
||||
|
||||
found:
|
||||
start = offset + (curr - pptr) * 32 + i;
|
||||
if (start >= size) {
|
||||
hfs_dbg(BITMAP, "bitmap full\n");
|
||||
goto out;
|
||||
}
|
||||
/* do any partial u32 at the start */
|
||||
len = min(size - start, len);
|
||||
while (1) {
|
||||
n |= mask;
|
||||
if (++i >= 32)
|
||||
break;
|
||||
mask >>= 1;
|
||||
if (!--len || n & mask)
|
||||
goto done;
|
||||
}
|
||||
if (!--len)
|
||||
goto done;
|
||||
*curr++ = cpu_to_be32(n);
|
||||
/* do full u32s */
|
||||
while (1) {
|
||||
while (curr < end) {
|
||||
n = be32_to_cpu(*curr);
|
||||
if (len < 32)
|
||||
goto last;
|
||||
if (n) {
|
||||
len = 32;
|
||||
goto last;
|
||||
}
|
||||
*curr++ = cpu_to_be32(0xffffffff);
|
||||
len -= 32;
|
||||
}
|
||||
set_page_dirty(page);
|
||||
kunmap(page);
|
||||
offset += PAGE_CACHE_BITS;
|
||||
page = read_mapping_page(mapping, offset / PAGE_CACHE_BITS,
|
||||
NULL);
|
||||
if (IS_ERR(page)) {
|
||||
start = size;
|
||||
goto out;
|
||||
}
|
||||
pptr = kmap(page);
|
||||
curr = pptr;
|
||||
end = pptr + PAGE_CACHE_BITS / 32;
|
||||
}
|
||||
last:
|
||||
/* do any partial u32 at end */
|
||||
mask = 1U << 31;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (n & mask)
|
||||
break;
|
||||
n |= mask;
|
||||
mask >>= 1;
|
||||
}
|
||||
done:
|
||||
*curr = cpu_to_be32(n);
|
||||
set_page_dirty(page);
|
||||
kunmap(page);
|
||||
*max = offset + (curr - pptr) * 32 + i - start;
|
||||
sbi->free_blocks -= *max;
|
||||
hfsplus_mark_mdb_dirty(sb);
|
||||
hfs_dbg(BITMAP, "-> %u,%u\n", start, *max);
|
||||
out:
|
||||
mutex_unlock(&sbi->alloc_mutex);
|
||||
return start;
|
||||
}
|
||||
|
||||
int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count)
|
||||
{
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
|
||||
struct page *page;
|
||||
struct address_space *mapping;
|
||||
__be32 *pptr, *curr, *end;
|
||||
u32 mask, len, pnr;
|
||||
int i;
|
||||
|
||||
/* is there any actual work to be done? */
|
||||
if (!count)
|
||||
return 0;
|
||||
|
||||
hfs_dbg(BITMAP, "block_free: %u,%u\n", offset, count);
|
||||
/* are all of the bits in range? */
|
||||
if ((offset + count) > sbi->total_blocks)
|
||||
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;
|
||||
len = count;
|
||||
|
||||
/* do any partial u32 at the start */
|
||||
i = offset % 32;
|
||||
if (i) {
|
||||
int j = 32 - i;
|
||||
mask = 0xffffffffU << j;
|
||||
if (j > count) {
|
||||
mask |= 0xffffffffU >> (i + count);
|
||||
*curr++ &= cpu_to_be32(mask);
|
||||
goto out;
|
||||
}
|
||||
*curr++ &= cpu_to_be32(mask);
|
||||
count -= j;
|
||||
}
|
||||
|
||||
/* do full u32s */
|
||||
while (1) {
|
||||
while (curr < end) {
|
||||
if (count < 32)
|
||||
goto done;
|
||||
*curr++ = 0;
|
||||
count -= 32;
|
||||
}
|
||||
if (!count)
|
||||
break;
|
||||
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;
|
||||
}
|
||||
done:
|
||||
/* do any partial u32 at end */
|
||||
if (count) {
|
||||
mask = 0xffffffffU >> count;
|
||||
*curr &= cpu_to_be32(mask);
|
||||
}
|
||||
out:
|
||||
set_page_dirty(page);
|
||||
kunmap(page);
|
||||
sbi->free_blocks += len;
|
||||
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;
|
||||
}
|
671
fs/hfsplus/bnode.c
Normal file
671
fs/hfsplus/bnode.c
Normal file
|
@ -0,0 +1,671 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/bnode.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* Handle basic btree node operations
|
||||
*/
|
||||
|
||||
#include <linux/string.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/swap.h>
|
||||
|
||||
#include "hfsplus_fs.h"
|
||||
#include "hfsplus_raw.h"
|
||||
|
||||
/* Copy a specified range of bytes from the raw data of a node */
|
||||
void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len)
|
||||
{
|
||||
struct page **pagep;
|
||||
int l;
|
||||
|
||||
off += node->page_offset;
|
||||
pagep = node->page + (off >> PAGE_CACHE_SHIFT);
|
||||
off &= ~PAGE_CACHE_MASK;
|
||||
|
||||
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_t(int, len, PAGE_CACHE_SIZE);
|
||||
memcpy(buf, kmap(*++pagep), l);
|
||||
kunmap(*pagep);
|
||||
}
|
||||
}
|
||||
|
||||
u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off)
|
||||
{
|
||||
__be16 data;
|
||||
/* TODO: optimize later... */
|
||||
hfs_bnode_read(node, &data, off, 2);
|
||||
return be16_to_cpu(data);
|
||||
}
|
||||
|
||||
u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off)
|
||||
{
|
||||
u8 data;
|
||||
/* TODO: optimize later... */
|
||||
hfs_bnode_read(node, &data, off, 1);
|
||||
return data;
|
||||
}
|
||||
|
||||
void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
int key_len;
|
||||
|
||||
tree = node->tree;
|
||||
if (node->type == HFS_NODE_LEAF ||
|
||||
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;
|
||||
|
||||
hfs_bnode_read(node, key, off, key_len);
|
||||
}
|
||||
|
||||
void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len)
|
||||
{
|
||||
struct page **pagep;
|
||||
int l;
|
||||
|
||||
off += node->page_offset;
|
||||
pagep = node->page + (off >> PAGE_CACHE_SHIFT);
|
||||
off &= ~PAGE_CACHE_MASK;
|
||||
|
||||
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_t(int, len, PAGE_CACHE_SIZE);
|
||||
memcpy(kmap(*++pagep), buf, l);
|
||||
set_page_dirty(*pagep);
|
||||
kunmap(*pagep);
|
||||
}
|
||||
}
|
||||
|
||||
void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data)
|
||||
{
|
||||
__be16 v = cpu_to_be16(data);
|
||||
/* TODO: optimize later... */
|
||||
hfs_bnode_write(node, &v, off, 2);
|
||||
}
|
||||
|
||||
void hfs_bnode_clear(struct hfs_bnode *node, int off, int len)
|
||||
{
|
||||
struct page **pagep;
|
||||
int l;
|
||||
|
||||
off += node->page_offset;
|
||||
pagep = node->page + (off >> PAGE_CACHE_SHIFT);
|
||||
off &= ~PAGE_CACHE_MASK;
|
||||
|
||||
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_t(int, len, PAGE_CACHE_SIZE);
|
||||
memset(kmap(*++pagep), 0, l);
|
||||
set_page_dirty(*pagep);
|
||||
kunmap(*pagep);
|
||||
}
|
||||
}
|
||||
|
||||
void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst,
|
||||
struct hfs_bnode *src_node, int src, int len)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
struct page **src_page, **dst_page;
|
||||
int l;
|
||||
|
||||
hfs_dbg(BNODE_MOD, "copybytes: %u,%u,%u\n", dst, src, len);
|
||||
if (!len)
|
||||
return;
|
||||
tree = src_node->tree;
|
||||
src += src_node->page_offset;
|
||||
dst += dst_node->page_offset;
|
||||
src_page = src_node->page + (src >> PAGE_CACHE_SHIFT);
|
||||
src &= ~PAGE_CACHE_MASK;
|
||||
dst_page = dst_node->page + (dst >> PAGE_CACHE_SHIFT);
|
||||
dst &= ~PAGE_CACHE_MASK;
|
||||
|
||||
if (src == dst) {
|
||||
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_t(int, len, PAGE_CACHE_SIZE);
|
||||
memcpy(kmap(*++dst_page), kmap(*++src_page), l);
|
||||
kunmap(*src_page);
|
||||
set_page_dirty(*dst_page);
|
||||
kunmap(*dst_page);
|
||||
}
|
||||
} else {
|
||||
void *src_ptr, *dst_ptr;
|
||||
|
||||
do {
|
||||
src_ptr = kmap(*src_page) + src;
|
||||
dst_ptr = kmap(*dst_page) + dst;
|
||||
if (PAGE_CACHE_SIZE - src < PAGE_CACHE_SIZE - dst) {
|
||||
l = PAGE_CACHE_SIZE - src;
|
||||
src = 0;
|
||||
dst += l;
|
||||
} else {
|
||||
l = PAGE_CACHE_SIZE - dst;
|
||||
src += l;
|
||||
dst = 0;
|
||||
}
|
||||
l = min(len, l);
|
||||
memcpy(dst_ptr, src_ptr, l);
|
||||
kunmap(*src_page);
|
||||
set_page_dirty(*dst_page);
|
||||
kunmap(*dst_page);
|
||||
if (!dst)
|
||||
dst_page++;
|
||||
else
|
||||
src_page++;
|
||||
} while ((len -= l));
|
||||
}
|
||||
}
|
||||
|
||||
void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len)
|
||||
{
|
||||
struct page **src_page, **dst_page;
|
||||
int l;
|
||||
|
||||
hfs_dbg(BNODE_MOD, "movebytes: %u,%u,%u\n", dst, src, len);
|
||||
if (!len)
|
||||
return;
|
||||
src += node->page_offset;
|
||||
dst += node->page_offset;
|
||||
if (dst > src) {
|
||||
src += len - 1;
|
||||
src_page = node->page + (src >> PAGE_CACHE_SHIFT);
|
||||
src = (src & ~PAGE_CACHE_MASK) + 1;
|
||||
dst += len - 1;
|
||||
dst_page = node->page + (dst >> PAGE_CACHE_SHIFT);
|
||||
dst = (dst & ~PAGE_CACHE_MASK) + 1;
|
||||
|
||||
if (src == dst) {
|
||||
while (src < len) {
|
||||
memmove(kmap(*dst_page), kmap(*src_page), src);
|
||||
kunmap(*src_page);
|
||||
set_page_dirty(*dst_page);
|
||||
kunmap(*dst_page);
|
||||
len -= src;
|
||||
src = PAGE_CACHE_SIZE;
|
||||
src_page--;
|
||||
dst_page--;
|
||||
}
|
||||
src -= len;
|
||||
memmove(kmap(*dst_page) + src,
|
||||
kmap(*src_page) + src, len);
|
||||
kunmap(*src_page);
|
||||
set_page_dirty(*dst_page);
|
||||
kunmap(*dst_page);
|
||||
} else {
|
||||
void *src_ptr, *dst_ptr;
|
||||
|
||||
do {
|
||||
src_ptr = kmap(*src_page) + src;
|
||||
dst_ptr = kmap(*dst_page) + dst;
|
||||
if (src < dst) {
|
||||
l = src;
|
||||
src = PAGE_CACHE_SIZE;
|
||||
dst -= l;
|
||||
} else {
|
||||
l = dst;
|
||||
src -= l;
|
||||
dst = PAGE_CACHE_SIZE;
|
||||
}
|
||||
l = min(len, l);
|
||||
memmove(dst_ptr - l, src_ptr - l, l);
|
||||
kunmap(*src_page);
|
||||
set_page_dirty(*dst_page);
|
||||
kunmap(*dst_page);
|
||||
if (dst == PAGE_CACHE_SIZE)
|
||||
dst_page--;
|
||||
else
|
||||
src_page--;
|
||||
} while ((len -= l));
|
||||
}
|
||||
} else {
|
||||
src_page = node->page + (src >> PAGE_CACHE_SHIFT);
|
||||
src &= ~PAGE_CACHE_MASK;
|
||||
dst_page = node->page + (dst >> PAGE_CACHE_SHIFT);
|
||||
dst &= ~PAGE_CACHE_MASK;
|
||||
|
||||
if (src == dst) {
|
||||
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_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);
|
||||
}
|
||||
} else {
|
||||
void *src_ptr, *dst_ptr;
|
||||
|
||||
do {
|
||||
src_ptr = kmap(*src_page) + src;
|
||||
dst_ptr = kmap(*dst_page) + dst;
|
||||
if (PAGE_CACHE_SIZE - src <
|
||||
PAGE_CACHE_SIZE - dst) {
|
||||
l = PAGE_CACHE_SIZE - src;
|
||||
src = 0;
|
||||
dst += l;
|
||||
} else {
|
||||
l = PAGE_CACHE_SIZE - dst;
|
||||
src += l;
|
||||
dst = 0;
|
||||
}
|
||||
l = min(len, l);
|
||||
memmove(dst_ptr, src_ptr, l);
|
||||
kunmap(*src_page);
|
||||
set_page_dirty(*dst_page);
|
||||
kunmap(*dst_page);
|
||||
if (!dst)
|
||||
dst_page++;
|
||||
else
|
||||
src_page++;
|
||||
} while ((len -= l));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void hfs_bnode_dump(struct hfs_bnode *node)
|
||||
{
|
||||
struct hfs_bnode_desc desc;
|
||||
__be32 cnid;
|
||||
int i, off, key_off;
|
||||
|
||||
hfs_dbg(BNODE_MOD, "bnode: %d\n", node->this);
|
||||
hfs_bnode_read(node, &desc, 0, sizeof(desc));
|
||||
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);
|
||||
hfs_dbg(BNODE_MOD, " %d", key_off);
|
||||
if (i && node->type == HFS_NODE_INDEX) {
|
||||
int tmp;
|
||||
|
||||
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;
|
||||
hfs_dbg_cont(BNODE_MOD, " (%d", tmp);
|
||||
hfs_bnode_read(node, &cnid, key_off + tmp, 4);
|
||||
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);
|
||||
hfs_dbg_cont(BNODE_MOD, " (%d)", tmp);
|
||||
}
|
||||
}
|
||||
hfs_dbg_cont(BNODE_MOD, "\n");
|
||||
}
|
||||
|
||||
void hfs_bnode_unlink(struct hfs_bnode *node)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
struct hfs_bnode *tmp;
|
||||
__be32 cnid;
|
||||
|
||||
tree = node->tree;
|
||||
if (node->prev) {
|
||||
tmp = hfs_bnode_find(tree, node->prev);
|
||||
if (IS_ERR(tmp))
|
||||
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_put(tmp);
|
||||
} else if (node->type == HFS_NODE_LEAF)
|
||||
tree->leaf_head = node->next;
|
||||
|
||||
if (node->next) {
|
||||
tmp = hfs_bnode_find(tree, node->next);
|
||||
if (IS_ERR(tmp))
|
||||
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_put(tmp);
|
||||
} else if (node->type == HFS_NODE_LEAF)
|
||||
tree->leaf_tail = node->prev;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
set_bit(HFS_BNODE_DELETED, &node->flags);
|
||||
}
|
||||
|
||||
static inline int hfs_bnode_hash(u32 num)
|
||||
{
|
||||
num = (num >> 16) + num;
|
||||
num += num >> 8;
|
||||
return num & (NODE_HASH_SIZE - 1);
|
||||
}
|
||||
|
||||
struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid)
|
||||
{
|
||||
struct hfs_bnode *node;
|
||||
|
||||
if (cnid >= tree->node_count) {
|
||||
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)
|
||||
return node;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct hfs_bnode *__hfs_bnode_create(struct hfs_btree *tree, u32 cnid)
|
||||
{
|
||||
struct super_block *sb;
|
||||
struct hfs_bnode *node, *node2;
|
||||
struct address_space *mapping;
|
||||
struct page *page;
|
||||
int size, block, i, hash;
|
||||
loff_t off;
|
||||
|
||||
if (cnid >= tree->node_count) {
|
||||
pr_err("request for non-existent node %d in B*Tree\n",
|
||||
cnid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sb = tree->inode->i_sb;
|
||||
size = sizeof(struct hfs_bnode) + tree->pages_per_bnode *
|
||||
sizeof(struct page *);
|
||||
node = kzalloc(size, GFP_KERNEL);
|
||||
if (!node)
|
||||
return NULL;
|
||||
node->tree = tree;
|
||||
node->this = cnid;
|
||||
set_bit(HFS_BNODE_NEW, &node->flags);
|
||||
atomic_set(&node->refcnt, 1);
|
||||
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);
|
||||
if (!node2) {
|
||||
hash = hfs_bnode_hash(cnid);
|
||||
node->next_hash = tree->node_hash[hash];
|
||||
tree->node_hash[hash] = node;
|
||||
tree->node_hash_cnt++;
|
||||
} else {
|
||||
spin_unlock(&tree->hash_lock);
|
||||
kfree(node);
|
||||
wait_event(node2->lock_wq,
|
||||
!test_bit(HFS_BNODE_NEW, &node2->flags));
|
||||
return node2;
|
||||
}
|
||||
spin_unlock(&tree->hash_lock);
|
||||
|
||||
mapping = tree->inode->i_mapping;
|
||||
off = (loff_t)cnid << tree->node_size_shift;
|
||||
block = off >> PAGE_CACHE_SHIFT;
|
||||
node->page_offset = off & ~PAGE_CACHE_MASK;
|
||||
for (i = 0; i < tree->pages_per_bnode; block++, i++) {
|
||||
page = read_mapping_page(mapping, block, NULL);
|
||||
if (IS_ERR(page))
|
||||
goto fail;
|
||||
if (PageError(page)) {
|
||||
page_cache_release(page);
|
||||
goto fail;
|
||||
}
|
||||
page_cache_release(page);
|
||||
node->page[i] = page;
|
||||
}
|
||||
|
||||
return node;
|
||||
fail:
|
||||
set_bit(HFS_BNODE_ERROR, &node->flags);
|
||||
return node;
|
||||
}
|
||||
|
||||
void hfs_bnode_unhash(struct hfs_bnode *node)
|
||||
{
|
||||
struct hfs_bnode **p;
|
||||
|
||||
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)
|
||||
;
|
||||
BUG_ON(!*p);
|
||||
*p = node->next_hash;
|
||||
node->tree->node_hash_cnt--;
|
||||
}
|
||||
|
||||
/* Load a particular node out of a tree */
|
||||
struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num)
|
||||
{
|
||||
struct hfs_bnode *node;
|
||||
struct hfs_bnode_desc *desc;
|
||||
int i, rec_off, off, next_off;
|
||||
int entry_size, key_size;
|
||||
|
||||
spin_lock(&tree->hash_lock);
|
||||
node = hfs_bnode_findhash(tree, num);
|
||||
if (node) {
|
||||
hfs_bnode_get(node);
|
||||
spin_unlock(&tree->hash_lock);
|
||||
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;
|
||||
}
|
||||
spin_unlock(&tree->hash_lock);
|
||||
node = __hfs_bnode_create(tree, num);
|
||||
if (!node)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
if (test_bit(HFS_BNODE_ERROR, &node->flags))
|
||||
goto node_error;
|
||||
if (!test_bit(HFS_BNODE_NEW, &node->flags))
|
||||
return node;
|
||||
|
||||
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);
|
||||
node->type = desc->type;
|
||||
node->height = desc->height;
|
||||
kunmap(node->page[0]);
|
||||
|
||||
switch (node->type) {
|
||||
case HFS_NODE_HEADER:
|
||||
case HFS_NODE_MAP:
|
||||
if (node->height != 0)
|
||||
goto node_error;
|
||||
break;
|
||||
case HFS_NODE_LEAF:
|
||||
if (node->height != 1)
|
||||
goto node_error;
|
||||
break;
|
||||
case HFS_NODE_INDEX:
|
||||
if (node->height <= 1 || node->height > tree->depth)
|
||||
goto node_error;
|
||||
break;
|
||||
default:
|
||||
goto node_error;
|
||||
}
|
||||
|
||||
rec_off = tree->node_size - 2;
|
||||
off = hfs_bnode_read_u16(node, rec_off);
|
||||
if (off != sizeof(struct hfs_bnode_desc))
|
||||
goto node_error;
|
||||
for (i = 1; i <= node->num_recs; off = next_off, i++) {
|
||||
rec_off -= 2;
|
||||
next_off = hfs_bnode_read_u16(node, rec_off);
|
||||
if (next_off <= off ||
|
||||
next_off > tree->node_size ||
|
||||
next_off & 1)
|
||||
goto node_error;
|
||||
entry_size = next_off - off;
|
||||
if (node->type != HFS_NODE_INDEX &&
|
||||
node->type != HFS_NODE_LEAF)
|
||||
continue;
|
||||
key_size = hfs_bnode_read_u16(node, off) + 2;
|
||||
if (key_size >= entry_size || key_size & 1)
|
||||
goto node_error;
|
||||
}
|
||||
clear_bit(HFS_BNODE_NEW, &node->flags);
|
||||
wake_up(&node->lock_wq);
|
||||
return node;
|
||||
|
||||
node_error:
|
||||
set_bit(HFS_BNODE_ERROR, &node->flags);
|
||||
clear_bit(HFS_BNODE_NEW, &node->flags);
|
||||
wake_up(&node->lock_wq);
|
||||
hfs_bnode_put(node);
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
|
||||
void hfs_bnode_free(struct hfs_bnode *node)
|
||||
{
|
||||
#if 0
|
||||
int i;
|
||||
|
||||
for (i = 0; i < node->tree->pages_per_bnode; i++)
|
||||
if (node->page[i])
|
||||
page_cache_release(node->page[i]);
|
||||
#endif
|
||||
kfree(node);
|
||||
}
|
||||
|
||||
struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num)
|
||||
{
|
||||
struct hfs_bnode *node;
|
||||
struct page **pagep;
|
||||
int i;
|
||||
|
||||
spin_lock(&tree->hash_lock);
|
||||
node = hfs_bnode_findhash(tree, num);
|
||||
spin_unlock(&tree->hash_lock);
|
||||
if (node) {
|
||||
pr_crit("new node %u already hashed?\n", num);
|
||||
WARN_ON(1);
|
||||
return node;
|
||||
}
|
||||
node = __hfs_bnode_create(tree, num);
|
||||
if (!node)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
if (test_bit(HFS_BNODE_ERROR, &node->flags)) {
|
||||
hfs_bnode_put(node);
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
|
||||
pagep = node->page;
|
||||
memset(kmap(*pagep) + node->page_offset, 0,
|
||||
min_t(int, PAGE_CACHE_SIZE, tree->node_size));
|
||||
set_page_dirty(*pagep);
|
||||
kunmap(*pagep);
|
||||
for (i = 1; i < tree->pages_per_bnode; i++) {
|
||||
memset(kmap(*++pagep), 0, PAGE_CACHE_SIZE);
|
||||
set_page_dirty(*pagep);
|
||||
kunmap(*pagep);
|
||||
}
|
||||
clear_bit(HFS_BNODE_NEW, &node->flags);
|
||||
wake_up(&node->lock_wq);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void hfs_bnode_get(struct hfs_bnode *node)
|
||||
{
|
||||
if (node) {
|
||||
atomic_inc(&node->refcnt);
|
||||
hfs_dbg(BNODE_REFS, "get_node(%d:%d): %d\n",
|
||||
node->tree->cnid, node->this,
|
||||
atomic_read(&node->refcnt));
|
||||
}
|
||||
}
|
||||
|
||||
/* Dispose of resources used by a node */
|
||||
void hfs_bnode_put(struct hfs_bnode *node)
|
||||
{
|
||||
if (node) {
|
||||
struct hfs_btree *tree = node->tree;
|
||||
int i;
|
||||
|
||||
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;
|
||||
for (i = 0; i < tree->pages_per_bnode; i++) {
|
||||
if (!node->page[i])
|
||||
continue;
|
||||
mark_page_accessed(node->page[i]);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
spin_unlock(&tree->hash_lock);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
527
fs/hfsplus/brec.c
Normal file
527
fs/hfsplus/brec.c
Normal file
|
@ -0,0 +1,527 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/brec.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* Handle individual btree records
|
||||
*/
|
||||
|
||||
#include "hfsplus_fs.h"
|
||||
#include "hfsplus_raw.h"
|
||||
|
||||
static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd);
|
||||
static int hfs_brec_update_parent(struct hfs_find_data *fd);
|
||||
static int hfs_btree_inc_height(struct hfs_btree *);
|
||||
|
||||
/* Get the length and offset of the given record in the given node */
|
||||
u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off)
|
||||
{
|
||||
__be16 retval[2];
|
||||
u16 dataoff;
|
||||
|
||||
dataoff = node->tree->node_size - (rec + 2) * 2;
|
||||
hfs_bnode_read(node, retval, dataoff, 4);
|
||||
*off = be16_to_cpu(retval[1]);
|
||||
return be16_to_cpu(retval[0]) - *off;
|
||||
}
|
||||
|
||||
/* Get the length of the key from a keyed record */
|
||||
u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec)
|
||||
{
|
||||
u16 retval, recoff;
|
||||
|
||||
if (node->type != HFS_NODE_INDEX && node->type != HFS_NODE_LEAF)
|
||||
return 0;
|
||||
|
||||
if ((node->type == HFS_NODE_INDEX) &&
|
||||
!(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);
|
||||
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) {
|
||||
pr_err("keylen %d too large\n",
|
||||
retval);
|
||||
retval = 0;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
struct hfs_bnode *node, *new_node;
|
||||
int size, key_len, rec;
|
||||
int data_off, end_off;
|
||||
int idx_rec_off, data_rec_off, end_rec_off;
|
||||
__be32 cnid;
|
||||
|
||||
tree = fd->tree;
|
||||
if (!fd->bnode) {
|
||||
if (!tree->root)
|
||||
hfs_btree_inc_height(tree);
|
||||
fd->bnode = hfs_bnode_find(tree, tree->leaf_head);
|
||||
if (IS_ERR(fd->bnode))
|
||||
return PTR_ERR(fd->bnode);
|
||||
fd->record = -1;
|
||||
}
|
||||
new_node = NULL;
|
||||
key_len = be16_to_cpu(fd->search_key->key_len) + 2;
|
||||
again:
|
||||
/* new record idx and complete record size */
|
||||
rec = fd->record + 1;
|
||||
size = key_len + entry_len;
|
||||
|
||||
node = fd->bnode;
|
||||
hfs_bnode_dump(node);
|
||||
/* get last offset */
|
||||
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;
|
||||
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");
|
||||
new_node = hfs_bnode_split(fd);
|
||||
if (IS_ERR(new_node))
|
||||
return PTR_ERR(new_node);
|
||||
goto again;
|
||||
}
|
||||
if (node->type == HFS_NODE_LEAF) {
|
||||
tree->leaf_count++;
|
||||
mark_inode_dirty(tree->inode);
|
||||
}
|
||||
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, end_rec_off, end_off + size);
|
||||
data_off = end_off;
|
||||
data_rec_off = end_rec_off + 2;
|
||||
idx_rec_off = tree->node_size - (rec + 1) * 2;
|
||||
if (idx_rec_off == data_rec_off)
|
||||
goto skip;
|
||||
/* move all following entries */
|
||||
do {
|
||||
data_off = hfs_bnode_read_u16(node, data_rec_off + 2);
|
||||
hfs_bnode_write_u16(node, data_rec_off, data_off + size);
|
||||
data_rec_off += 2;
|
||||
} while (data_rec_off < idx_rec_off);
|
||||
|
||||
/* move data away */
|
||||
hfs_bnode_move(node, data_off + size, data_off,
|
||||
end_off - data_off);
|
||||
|
||||
skip:
|
||||
hfs_bnode_write(node, fd->search_key, data_off, key_len);
|
||||
hfs_bnode_write(node, entry, data_off + key_len, entry_len);
|
||||
hfs_bnode_dump(node);
|
||||
|
||||
/*
|
||||
* update parent key if we inserted a key
|
||||
* at the start of the node and it is not the new node
|
||||
*/
|
||||
if (!rec && new_node != node) {
|
||||
hfs_bnode_read_key(node, fd->search_key, data_off + size);
|
||||
hfs_brec_update_parent(fd);
|
||||
}
|
||||
|
||||
if (new_node) {
|
||||
hfs_bnode_put(fd->bnode);
|
||||
if (!new_node->parent) {
|
||||
hfs_btree_inc_height(tree);
|
||||
new_node->parent = tree->root;
|
||||
}
|
||||
fd->bnode = hfs_bnode_find(tree, new_node->parent);
|
||||
|
||||
/* create index data entry */
|
||||
cnid = cpu_to_be32(new_node->this);
|
||||
entry = &cnid;
|
||||
entry_len = sizeof(cnid);
|
||||
|
||||
/* get index key */
|
||||
hfs_bnode_read_key(new_node, fd->search_key, 14);
|
||||
__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) ||
|
||||
(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);
|
||||
key_len = tree->max_key_len + 2;
|
||||
}
|
||||
goto again;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hfs_brec_remove(struct hfs_find_data *fd)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
struct hfs_bnode *node, *parent;
|
||||
int end_off, rec_off, data_off, size;
|
||||
|
||||
tree = fd->tree;
|
||||
node = fd->bnode;
|
||||
again:
|
||||
rec_off = tree->node_size - (fd->record + 2) * 2;
|
||||
end_off = tree->node_size - (node->num_recs + 1) * 2;
|
||||
|
||||
if (node->type == HFS_NODE_LEAF) {
|
||||
tree->leaf_count--;
|
||||
mark_inode_dirty(tree->inode);
|
||||
}
|
||||
hfs_bnode_dump(node);
|
||||
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)
|
||||
return 0;
|
||||
parent = hfs_bnode_find(tree, node->parent);
|
||||
if (IS_ERR(parent))
|
||||
return PTR_ERR(parent);
|
||||
hfs_bnode_put(node);
|
||||
node = fd->bnode = parent;
|
||||
|
||||
__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);
|
||||
|
||||
if (rec_off == end_off)
|
||||
goto skip;
|
||||
size = fd->keylength + fd->entrylength;
|
||||
|
||||
do {
|
||||
data_off = hfs_bnode_read_u16(node, rec_off);
|
||||
hfs_bnode_write_u16(node, rec_off + 2, data_off - size);
|
||||
rec_off -= 2;
|
||||
} while (rec_off >= end_off);
|
||||
|
||||
/* fill hole */
|
||||
hfs_bnode_move(node, fd->keyoffset, fd->keyoffset + size,
|
||||
data_off - fd->keyoffset - size);
|
||||
skip:
|
||||
hfs_bnode_dump(node);
|
||||
if (!fd->record)
|
||||
hfs_brec_update_parent(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
struct hfs_bnode *node, *new_node, *next_node;
|
||||
struct hfs_bnode_desc node_desc;
|
||||
int num_recs, new_rec_off, new_off, old_rec_off;
|
||||
int data_start, data_end, size;
|
||||
|
||||
tree = fd->tree;
|
||||
node = fd->bnode;
|
||||
new_node = hfs_bmap_alloc(tree);
|
||||
if (IS_ERR(new_node))
|
||||
return new_node;
|
||||
hfs_bnode_get(node);
|
||||
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;
|
||||
new_node->parent = node->parent;
|
||||
new_node->type = node->type;
|
||||
new_node->height = node->height;
|
||||
|
||||
if (node->next)
|
||||
next_node = hfs_bnode_find(tree, node->next);
|
||||
else
|
||||
next_node = NULL;
|
||||
|
||||
if (IS_ERR(next_node)) {
|
||||
hfs_bnode_put(node);
|
||||
hfs_bnode_put(new_node);
|
||||
return next_node;
|
||||
}
|
||||
|
||||
size = tree->node_size / 2 - node->num_recs * 2 - 14;
|
||||
old_rec_off = tree->node_size - 4;
|
||||
num_recs = 1;
|
||||
for (;;) {
|
||||
data_start = hfs_bnode_read_u16(node, old_rec_off);
|
||||
if (data_start > size)
|
||||
break;
|
||||
old_rec_off -= 2;
|
||||
if (++num_recs < node->num_recs)
|
||||
continue;
|
||||
/* panic? */
|
||||
hfs_bnode_put(node);
|
||||
hfs_bnode_put(new_node);
|
||||
if (next_node)
|
||||
hfs_bnode_put(next_node);
|
||||
return ERR_PTR(-ENOSPC);
|
||||
}
|
||||
|
||||
if (fd->record + 1 < num_recs) {
|
||||
/* new record is in the lower half,
|
||||
* so leave some more space there
|
||||
*/
|
||||
old_rec_off += 2;
|
||||
num_recs--;
|
||||
data_start = hfs_bnode_read_u16(node, old_rec_off);
|
||||
} else {
|
||||
hfs_bnode_put(node);
|
||||
hfs_bnode_get(new_node);
|
||||
fd->bnode = new_node;
|
||||
fd->record -= num_recs;
|
||||
fd->keyoffset -= data_start - 14;
|
||||
fd->entryoffset -= data_start - 14;
|
||||
}
|
||||
new_node->num_recs = node->num_recs - num_recs;
|
||||
node->num_recs = num_recs;
|
||||
|
||||
new_rec_off = tree->node_size - 2;
|
||||
new_off = 14;
|
||||
size = data_start - new_off;
|
||||
num_recs = new_node->num_recs;
|
||||
data_end = data_start;
|
||||
while (num_recs) {
|
||||
hfs_bnode_write_u16(new_node, new_rec_off, new_off);
|
||||
old_rec_off -= 2;
|
||||
new_rec_off -= 2;
|
||||
data_end = hfs_bnode_read_u16(node, old_rec_off);
|
||||
new_off = data_end - size;
|
||||
num_recs--;
|
||||
}
|
||||
hfs_bnode_write_u16(new_node, new_rec_off, new_off);
|
||||
hfs_bnode_copy(new_node, 14, node, data_start, data_end - data_start);
|
||||
|
||||
/* update new bnode header */
|
||||
node_desc.next = cpu_to_be32(new_node->next);
|
||||
node_desc.prev = cpu_to_be32(new_node->prev);
|
||||
node_desc.type = new_node->type;
|
||||
node_desc.height = new_node->height;
|
||||
node_desc.num_recs = cpu_to_be16(new_node->num_recs);
|
||||
node_desc.reserved = 0;
|
||||
hfs_bnode_write(new_node, &node_desc, 0, sizeof(node_desc));
|
||||
|
||||
/* update previous bnode header */
|
||||
node->next = new_node->this;
|
||||
hfs_bnode_read(node, &node_desc, 0, sizeof(node_desc));
|
||||
node_desc.next = cpu_to_be32(node->next);
|
||||
node_desc.num_recs = cpu_to_be16(node->num_recs);
|
||||
hfs_bnode_write(node, &node_desc, 0, sizeof(node_desc));
|
||||
|
||||
/* update next bnode header */
|
||||
if (next_node) {
|
||||
next_node->prev = new_node->this;
|
||||
hfs_bnode_read(next_node, &node_desc, 0, sizeof(node_desc));
|
||||
node_desc.prev = cpu_to_be32(next_node->prev);
|
||||
hfs_bnode_write(next_node, &node_desc, 0, sizeof(node_desc));
|
||||
hfs_bnode_put(next_node);
|
||||
} else if (node->this == tree->leaf_tail) {
|
||||
/* if there is no next node, this might be the new tail */
|
||||
tree->leaf_tail = new_node->this;
|
||||
mark_inode_dirty(tree->inode);
|
||||
}
|
||||
|
||||
hfs_bnode_dump(node);
|
||||
hfs_bnode_dump(new_node);
|
||||
hfs_bnode_put(node);
|
||||
|
||||
return new_node;
|
||||
}
|
||||
|
||||
static int hfs_brec_update_parent(struct hfs_find_data *fd)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
struct hfs_bnode *node, *new_node, *parent;
|
||||
int newkeylen, diff;
|
||||
int rec, rec_off, end_rec_off;
|
||||
int start_off, end_off;
|
||||
|
||||
tree = fd->tree;
|
||||
node = fd->bnode;
|
||||
new_node = NULL;
|
||||
if (!node->parent)
|
||||
return 0;
|
||||
|
||||
again:
|
||||
parent = hfs_bnode_find(tree, node->parent);
|
||||
if (IS_ERR(parent))
|
||||
return PTR_ERR(parent);
|
||||
__hfs_brec_find(parent, fd, hfs_find_rec_by_key);
|
||||
if (fd->record < 0)
|
||||
return -ENOENT;
|
||||
hfs_bnode_dump(parent);
|
||||
rec = fd->record;
|
||||
|
||||
/* size difference between old and new key */
|
||||
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;
|
||||
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;
|
||||
diff = newkeylen - fd->keylength;
|
||||
if (!diff)
|
||||
goto skip;
|
||||
if (diff > 0) {
|
||||
end_off = hfs_bnode_read_u16(parent, end_rec_off);
|
||||
if (end_rec_off - end_off < diff) {
|
||||
|
||||
hfs_dbg(BNODE_MOD, "splitting index node\n");
|
||||
fd->bnode = parent;
|
||||
new_node = hfs_bnode_split(fd);
|
||||
if (IS_ERR(new_node))
|
||||
return PTR_ERR(new_node);
|
||||
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_off = start_off = hfs_bnode_read_u16(parent, rec_off);
|
||||
hfs_bnode_write_u16(parent, rec_off, start_off + diff);
|
||||
start_off -= 4; /* move previous cnid too */
|
||||
|
||||
while (rec_off > end_rec_off) {
|
||||
rec_off -= 2;
|
||||
end_off = hfs_bnode_read_u16(parent, rec_off);
|
||||
hfs_bnode_write_u16(parent, rec_off, end_off + diff);
|
||||
}
|
||||
hfs_bnode_move(parent, start_off + diff, start_off,
|
||||
end_off - start_off);
|
||||
skip:
|
||||
hfs_bnode_copy(parent, fd->keyoffset, node, 14, newkeylen);
|
||||
hfs_bnode_dump(parent);
|
||||
|
||||
hfs_bnode_put(node);
|
||||
node = parent;
|
||||
|
||||
if (new_node) {
|
||||
__be32 cnid;
|
||||
|
||||
fd->bnode = hfs_bnode_find(tree, new_node->parent);
|
||||
/* create index key and entry */
|
||||
hfs_bnode_read_key(new_node, fd->search_key, 14);
|
||||
cnid = cpu_to_be32(new_node->this);
|
||||
|
||||
__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);
|
||||
|
||||
if (!rec) {
|
||||
if (new_node == node)
|
||||
goto out;
|
||||
/* restore search_key */
|
||||
hfs_bnode_read_key(node, fd->search_key, 14);
|
||||
}
|
||||
}
|
||||
|
||||
if (!rec && node->parent)
|
||||
goto again;
|
||||
out:
|
||||
fd->bnode = node;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hfs_btree_inc_height(struct hfs_btree *tree)
|
||||
{
|
||||
struct hfs_bnode *node, *new_node;
|
||||
struct hfs_bnode_desc node_desc;
|
||||
int key_size, rec;
|
||||
__be32 cnid;
|
||||
|
||||
node = NULL;
|
||||
if (tree->root) {
|
||||
node = hfs_bnode_find(tree, tree->root);
|
||||
if (IS_ERR(node))
|
||||
return PTR_ERR(node);
|
||||
}
|
||||
new_node = hfs_bmap_alloc(tree);
|
||||
if (IS_ERR(new_node)) {
|
||||
hfs_bnode_put(node);
|
||||
return PTR_ERR(new_node);
|
||||
}
|
||||
|
||||
tree->root = new_node->this;
|
||||
if (!tree->depth) {
|
||||
tree->leaf_head = tree->leaf_tail = new_node->this;
|
||||
new_node->type = HFS_NODE_LEAF;
|
||||
new_node->num_recs = 0;
|
||||
} else {
|
||||
new_node->type = HFS_NODE_INDEX;
|
||||
new_node->num_recs = 1;
|
||||
}
|
||||
new_node->parent = 0;
|
||||
new_node->next = 0;
|
||||
new_node->prev = 0;
|
||||
new_node->height = ++tree->depth;
|
||||
|
||||
node_desc.next = cpu_to_be32(new_node->next);
|
||||
node_desc.prev = cpu_to_be32(new_node->prev);
|
||||
node_desc.type = new_node->type;
|
||||
node_desc.height = new_node->height;
|
||||
node_desc.num_recs = cpu_to_be16(new_node->num_recs);
|
||||
node_desc.reserved = 0;
|
||||
hfs_bnode_write(new_node, &node_desc, 0, sizeof(node_desc));
|
||||
|
||||
rec = tree->node_size - 2;
|
||||
hfs_bnode_write_u16(new_node, rec, 14);
|
||||
|
||||
if (node) {
|
||||
/* insert old root idx into new root */
|
||||
node->parent = tree->root;
|
||||
if (node->type == HFS_NODE_LEAF ||
|
||||
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) &&
|
||||
(tree->cnid != HFSPLUS_ATTR_CNID)) {
|
||||
key_size = tree->max_key_len + 2;
|
||||
hfs_bnode_write_u16(new_node, 14, tree->max_key_len);
|
||||
}
|
||||
cnid = cpu_to_be32(node->this);
|
||||
hfs_bnode_write(new_node, &cnid, 14 + key_size, 4);
|
||||
|
||||
rec -= 2;
|
||||
hfs_bnode_write_u16(new_node, rec, 14 + key_size + 4);
|
||||
|
||||
hfs_bnode_put(node);
|
||||
}
|
||||
hfs_bnode_put(new_node);
|
||||
mark_inode_dirty(tree->inode);
|
||||
|
||||
return 0;
|
||||
}
|
497
fs/hfsplus/btree.c
Normal file
497
fs/hfsplus/btree.c
Normal file
|
@ -0,0 +1,497 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/btree.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* Handle opening/closing btree
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/log2.h>
|
||||
|
||||
#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)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
struct hfs_btree_header_rec *head;
|
||||
struct address_space *mapping;
|
||||
struct inode *inode;
|
||||
struct page *page;
|
||||
unsigned int size;
|
||||
|
||||
tree = kzalloc(sizeof(*tree), GFP_KERNEL);
|
||||
if (!tree)
|
||||
return NULL;
|
||||
|
||||
mutex_init(&tree->tree_lock);
|
||||
spin_lock_init(&tree->hash_lock);
|
||||
tree->sb = sb;
|
||||
tree->cnid = id;
|
||||
inode = hfsplus_iget(sb, id);
|
||||
if (IS_ERR(inode))
|
||||
goto free_tree;
|
||||
tree->inode = inode;
|
||||
|
||||
if (!HFSPLUS_I(tree->inode)->first_blocks) {
|
||||
pr_err("invalid btree extent records (0 size)\n");
|
||||
goto free_inode;
|
||||
}
|
||||
|
||||
mapping = tree->inode->i_mapping;
|
||||
page = read_mapping_page(mapping, 0, NULL);
|
||||
if (IS_ERR(page))
|
||||
goto free_inode;
|
||||
|
||||
/* Load the header */
|
||||
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);
|
||||
tree->leaf_tail = be32_to_cpu(head->leaf_tail);
|
||||
tree->node_count = be32_to_cpu(head->node_count);
|
||||
tree->free_nodes = be32_to_cpu(head->free_nodes);
|
||||
tree->attributes = be32_to_cpu(head->attributes);
|
||||
tree->node_size = be16_to_cpu(head->node_size);
|
||||
tree->max_key_len = be16_to_cpu(head->max_key_len);
|
||||
tree->depth = be16_to_cpu(head->depth);
|
||||
|
||||
/* Verify the tree and set the correct compare function */
|
||||
switch (id) {
|
||||
case HFSPLUS_EXT_CNID:
|
||||
if (tree->max_key_len != HFSPLUS_EXT_KEYLEN - sizeof(u16)) {
|
||||
pr_err("invalid extent max_key_len %d\n",
|
||||
tree->max_key_len);
|
||||
goto fail_page;
|
||||
}
|
||||
if (tree->attributes & HFS_TREE_VARIDXKEYS) {
|
||||
pr_err("invalid extent btree flag\n");
|
||||
goto fail_page;
|
||||
}
|
||||
|
||||
tree->keycmp = hfsplus_ext_cmp_key;
|
||||
break;
|
||||
case HFSPLUS_CAT_CNID:
|
||||
if (tree->max_key_len != HFSPLUS_CAT_KEYLEN - sizeof(u16)) {
|
||||
pr_err("invalid catalog max_key_len %d\n",
|
||||
tree->max_key_len);
|
||||
goto fail_page;
|
||||
}
|
||||
if (!(tree->attributes & HFS_TREE_VARIDXKEYS)) {
|
||||
pr_err("invalid catalog btree flag\n");
|
||||
goto fail_page;
|
||||
}
|
||||
|
||||
if (test_bit(HFSPLUS_SB_HFSX, &HFSPLUS_SB(sb)->flags) &&
|
||||
(head->key_type == HFSPLUS_KEY_BINARY))
|
||||
tree->keycmp = hfsplus_cat_bin_cmp_key;
|
||||
else {
|
||||
tree->keycmp = hfsplus_cat_case_cmp_key;
|
||||
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:
|
||||
pr_err("unknown B*Tree requested\n");
|
||||
goto fail_page;
|
||||
}
|
||||
|
||||
if (!(tree->attributes & HFS_TREE_BIGKEYS)) {
|
||||
pr_err("invalid btree flag\n");
|
||||
goto fail_page;
|
||||
}
|
||||
|
||||
size = tree->node_size;
|
||||
if (!is_power_of_2(size))
|
||||
goto fail_page;
|
||||
if (!tree->node_count)
|
||||
goto fail_page;
|
||||
|
||||
tree->node_size_shift = ffs(size) - 1;
|
||||
|
||||
tree->pages_per_bnode =
|
||||
(tree->node_size + PAGE_CACHE_SIZE - 1) >>
|
||||
PAGE_CACHE_SHIFT;
|
||||
|
||||
kunmap(page);
|
||||
page_cache_release(page);
|
||||
return tree;
|
||||
|
||||
fail_page:
|
||||
page_cache_release(page);
|
||||
free_inode:
|
||||
tree->inode->i_mapping->a_ops = &hfsplus_aops;
|
||||
iput(tree->inode);
|
||||
free_tree:
|
||||
kfree(tree);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Release resources used by a btree */
|
||||
void hfs_btree_close(struct hfs_btree *tree)
|
||||
{
|
||||
struct hfs_bnode *node;
|
||||
int i;
|
||||
|
||||
if (!tree)
|
||||
return;
|
||||
|
||||
for (i = 0; i < NODE_HASH_SIZE; i++) {
|
||||
while ((node = tree->node_hash[i])) {
|
||||
tree->node_hash[i] = node->next_hash;
|
||||
if (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--;
|
||||
}
|
||||
}
|
||||
iput(tree->inode);
|
||||
kfree(tree);
|
||||
}
|
||||
|
||||
int hfs_btree_write(struct hfs_btree *tree)
|
||||
{
|
||||
struct hfs_btree_header_rec *head;
|
||||
struct hfs_bnode *node;
|
||||
struct page *page;
|
||||
|
||||
node = hfs_bnode_find(tree, 0);
|
||||
if (IS_ERR(node))
|
||||
/* panic? */
|
||||
return -EIO;
|
||||
/* Load the header */
|
||||
page = node->page[0];
|
||||
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);
|
||||
head->leaf_head = cpu_to_be32(tree->leaf_head);
|
||||
head->leaf_tail = cpu_to_be32(tree->leaf_tail);
|
||||
head->node_count = cpu_to_be32(tree->node_count);
|
||||
head->free_nodes = cpu_to_be32(tree->free_nodes);
|
||||
head->attributes = cpu_to_be32(tree->attributes);
|
||||
head->depth = cpu_to_be16(tree->depth);
|
||||
|
||||
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)
|
||||
{
|
||||
struct hfs_btree *tree = prev->tree;
|
||||
struct hfs_bnode *node;
|
||||
struct hfs_bnode_desc desc;
|
||||
__be32 cnid;
|
||||
|
||||
node = hfs_bnode_create(tree, idx);
|
||||
if (IS_ERR(node))
|
||||
return node;
|
||||
|
||||
tree->free_nodes--;
|
||||
prev->next = idx;
|
||||
cnid = cpu_to_be32(idx);
|
||||
hfs_bnode_write(prev, &cnid, offsetof(struct hfs_bnode_desc, next), 4);
|
||||
|
||||
node->type = HFS_NODE_MAP;
|
||||
node->num_recs = 1;
|
||||
hfs_bnode_clear(node, 0, tree->node_size);
|
||||
desc.next = 0;
|
||||
desc.prev = 0;
|
||||
desc.type = HFS_NODE_MAP;
|
||||
desc.height = 0;
|
||||
desc.num_recs = cpu_to_be16(1);
|
||||
desc.reserved = 0;
|
||||
hfs_bnode_write(node, &desc, 0, sizeof(desc));
|
||||
hfs_bnode_write_u16(node, 14, 0x8000);
|
||||
hfs_bnode_write_u16(node, tree->node_size - 2, 14);
|
||||
hfs_bnode_write_u16(node, tree->node_size - 4, tree->node_size - 6);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
|
||||
{
|
||||
struct hfs_bnode *node, *next_node;
|
||||
struct page **pagep;
|
||||
u32 nidx, idx;
|
||||
unsigned off;
|
||||
u16 off16;
|
||||
u16 len;
|
||||
u8 *data, byte, m;
|
||||
int i;
|
||||
|
||||
while (!tree->free_nodes) {
|
||||
struct inode *inode = tree->inode;
|
||||
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
|
||||
u32 count;
|
||||
int res;
|
||||
|
||||
res = hfsplus_file_extend(inode, hfs_bnode_need_zeroout(tree));
|
||||
if (res)
|
||||
return ERR_PTR(res);
|
||||
hip->phys_size = inode->i_size =
|
||||
(loff_t)hip->alloc_blocks <<
|
||||
HFSPLUS_SB(tree->sb)->alloc_blksz_shift;
|
||||
hip->fs_blocks =
|
||||
hip->alloc_blocks << HFSPLUS_SB(tree->sb)->fs_shift;
|
||||
inode_set_bytes(inode, inode->i_size);
|
||||
count = inode->i_size >> tree->node_size_shift;
|
||||
tree->free_nodes = count - tree->node_count;
|
||||
tree->node_count = count;
|
||||
}
|
||||
|
||||
nidx = 0;
|
||||
node = hfs_bnode_find(tree, nidx);
|
||||
if (IS_ERR(node))
|
||||
return node;
|
||||
len = hfs_brec_lenoff(node, 2, &off16);
|
||||
off = off16;
|
||||
|
||||
off += node->page_offset;
|
||||
pagep = node->page + (off >> PAGE_CACHE_SHIFT);
|
||||
data = kmap(*pagep);
|
||||
off &= ~PAGE_CACHE_MASK;
|
||||
idx = 0;
|
||||
|
||||
for (;;) {
|
||||
while (len) {
|
||||
byte = data[off];
|
||||
if (byte != 0xff) {
|
||||
for (m = 0x80, i = 0; i < 8; m >>= 1, i++) {
|
||||
if (!(byte & m)) {
|
||||
idx += i;
|
||||
data[off] |= m;
|
||||
set_page_dirty(*pagep);
|
||||
kunmap(*pagep);
|
||||
tree->free_nodes--;
|
||||
mark_inode_dirty(tree->inode);
|
||||
hfs_bnode_put(node);
|
||||
return hfs_bnode_create(tree,
|
||||
idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (++off >= PAGE_CACHE_SIZE) {
|
||||
kunmap(*pagep);
|
||||
data = kmap(*++pagep);
|
||||
off = 0;
|
||||
}
|
||||
idx += 8;
|
||||
len--;
|
||||
}
|
||||
kunmap(*pagep);
|
||||
nidx = node->next;
|
||||
if (!nidx) {
|
||||
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);
|
||||
hfs_bnode_put(node);
|
||||
if (IS_ERR(next_node))
|
||||
return next_node;
|
||||
node = next_node;
|
||||
|
||||
len = hfs_brec_lenoff(node, 0, &off16);
|
||||
off = off16;
|
||||
off += node->page_offset;
|
||||
pagep = node->page + (off >> PAGE_CACHE_SHIFT);
|
||||
data = kmap(*pagep);
|
||||
off &= ~PAGE_CACHE_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
void hfs_bmap_free(struct hfs_bnode *node)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
struct page *page;
|
||||
u16 off, len;
|
||||
u32 nidx;
|
||||
u8 *data, byte, m;
|
||||
|
||||
hfs_dbg(BNODE_MOD, "btree_free_node: %u\n", node->this);
|
||||
BUG_ON(!node->this);
|
||||
tree = node->tree;
|
||||
nidx = node->this;
|
||||
node = hfs_bnode_find(tree, 0);
|
||||
if (IS_ERR(node))
|
||||
return;
|
||||
len = hfs_brec_lenoff(node, 2, &off);
|
||||
while (nidx >= len * 8) {
|
||||
u32 i;
|
||||
|
||||
nidx -= len * 8;
|
||||
i = node->next;
|
||||
hfs_bnode_put(node);
|
||||
if (!i) {
|
||||
/* panic */;
|
||||
pr_crit("unable to free bnode %u. "
|
||||
"bmap not found!\n",
|
||||
node->this);
|
||||
return;
|
||||
}
|
||||
node = hfs_bnode_find(tree, i);
|
||||
if (IS_ERR(node))
|
||||
return;
|
||||
if (node->type != HFS_NODE_MAP) {
|
||||
/* panic */;
|
||||
pr_crit("invalid bmap found! "
|
||||
"(%u,%d)\n",
|
||||
node->this, node->type);
|
||||
hfs_bnode_put(node);
|
||||
return;
|
||||
}
|
||||
len = hfs_brec_lenoff(node, 0, &off);
|
||||
}
|
||||
off += node->page_offset + nidx / 8;
|
||||
page = node->page[off >> PAGE_CACHE_SHIFT];
|
||||
data = kmap(page);
|
||||
off &= ~PAGE_CACHE_MASK;
|
||||
m = 1 << (~nidx & 7);
|
||||
byte = data[off];
|
||||
if (!(byte & m)) {
|
||||
pr_crit("trying to free free bnode "
|
||||
"%u(%d)\n",
|
||||
node->this, node->type);
|
||||
kunmap(page);
|
||||
hfs_bnode_put(node);
|
||||
return;
|
||||
}
|
||||
data[off] = byte & ~m;
|
||||
set_page_dirty(page);
|
||||
kunmap(page);
|
||||
hfs_bnode_put(node);
|
||||
tree->free_nodes++;
|
||||
mark_inode_dirty(tree->inode);
|
||||
}
|
479
fs/hfsplus/catalog.c
Normal file
479
fs/hfsplus/catalog.c
Normal file
|
@ -0,0 +1,479 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/catalog.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* Handling of catalog records
|
||||
*/
|
||||
|
||||
|
||||
#include "hfsplus_fs.h"
|
||||
#include "hfsplus_raw.h"
|
||||
|
||||
int hfsplus_cat_case_cmp_key(const hfsplus_btree_key *k1,
|
||||
const hfsplus_btree_key *k2)
|
||||
{
|
||||
__be32 k1p, k2p;
|
||||
|
||||
k1p = k1->cat.parent;
|
||||
k2p = k2->cat.parent;
|
||||
if (k1p != k2p)
|
||||
return be32_to_cpu(k1p) < be32_to_cpu(k2p) ? -1 : 1;
|
||||
|
||||
return hfsplus_strcasecmp(&k1->cat.name, &k2->cat.name);
|
||||
}
|
||||
|
||||
int hfsplus_cat_bin_cmp_key(const hfsplus_btree_key *k1,
|
||||
const hfsplus_btree_key *k2)
|
||||
{
|
||||
__be32 k1p, k2p;
|
||||
|
||||
k1p = k1->cat.parent;
|
||||
k2p = k2->cat.parent;
|
||||
if (k1p != k2p)
|
||||
return be32_to_cpu(k1p) < be32_to_cpu(k2p) ? -1 : 1;
|
||||
|
||||
return hfsplus_strcmp(&k1->cat.name, &k2->cat.name);
|
||||
}
|
||||
|
||||
void hfsplus_cat_build_key(struct super_block *sb, hfsplus_btree_key *key,
|
||||
u32 parent, struct qstr *str)
|
||||
{
|
||||
int len;
|
||||
|
||||
key->cat.parent = cpu_to_be32(parent);
|
||||
if (str) {
|
||||
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;
|
||||
len = 0;
|
||||
}
|
||||
key->key_len = cpu_to_be16(6 + 2 * len);
|
||||
}
|
||||
|
||||
static void hfsplus_cat_build_key_uni(hfsplus_btree_key *key, u32 parent,
|
||||
struct hfsplus_unistr *name)
|
||||
{
|
||||
int ustrlen;
|
||||
|
||||
ustrlen = be16_to_cpu(name->length);
|
||||
key->cat.parent = cpu_to_be32(parent);
|
||||
key->cat.name.length = cpu_to_be16(ustrlen);
|
||||
ustrlen *= 2;
|
||||
memcpy(key->cat.name.unicode, name->unicode, ustrlen);
|
||||
key->key_len = cpu_to_be16(6 + ustrlen);
|
||||
}
|
||||
|
||||
void hfsplus_cat_set_perms(struct inode *inode, struct hfsplus_perm *perms)
|
||||
{
|
||||
if (inode->i_flags & S_IMMUTABLE)
|
||||
perms->rootflags |= HFSPLUS_FLG_IMMUTABLE;
|
||||
else
|
||||
perms->rootflags &= ~HFSPLUS_FLG_IMMUTABLE;
|
||||
if (inode->i_flags & S_APPEND)
|
||||
perms->rootflags |= HFSPLUS_FLG_APPEND;
|
||||
else
|
||||
perms->rootflags &= ~HFSPLUS_FLG_APPEND;
|
||||
|
||||
perms->userflags = HFSPLUS_I(inode)->userflags;
|
||||
perms->mode = cpu_to_be16(inode->i_mode);
|
||||
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);
|
||||
else if (S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode))
|
||||
perms->dev = cpu_to_be32(inode->i_rdev);
|
||||
else
|
||||
perms->dev = 0;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (S_ISDIR(inode->i_mode)) {
|
||||
struct hfsplus_cat_folder *folder;
|
||||
|
||||
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 =
|
||||
folder->content_mod_date =
|
||||
folder->attribute_mod_date =
|
||||
folder->access_date = hfsp_now2mt();
|
||||
hfsplus_cat_set_perms(inode, &folder->permissions);
|
||||
if (inode == sbi->hidden_dir)
|
||||
/* invisible and namelocked */
|
||||
folder->user_info.frFlags = cpu_to_be16(0x5000);
|
||||
return sizeof(*folder);
|
||||
} else {
|
||||
struct hfsplus_cat_file *file;
|
||||
|
||||
file = &entry->file;
|
||||
memset(file, 0, sizeof(*file));
|
||||
file->type = cpu_to_be16(HFSPLUS_FILE);
|
||||
file->flags = cpu_to_be16(HFSPLUS_FILE_THREAD_EXISTS);
|
||||
file->id = cpu_to_be32(cnid);
|
||||
HFSPLUS_I(inode)->create_date =
|
||||
file->create_date =
|
||||
file->content_mod_date =
|
||||
file->attribute_mod_date =
|
||||
file->access_date = hfsp_now2mt();
|
||||
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);
|
||||
} else {
|
||||
file->user_info.fdType =
|
||||
cpu_to_be32(sbi->type);
|
||||
file->user_info.fdCreator =
|
||||
cpu_to_be32(sbi->creator);
|
||||
}
|
||||
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);
|
||||
}
|
||||
return sizeof(*file);
|
||||
}
|
||||
}
|
||||
|
||||
static int hfsplus_fill_cat_thread(struct super_block *sb,
|
||||
hfsplus_cat_entry *entry, int type,
|
||||
u32 parentid, struct qstr *str)
|
||||
{
|
||||
entry->type = cpu_to_be16(type);
|
||||
entry->thread.reserved = 0;
|
||||
entry->thread.parentID = cpu_to_be32(parentid);
|
||||
hfsplus_asc2uni(sb, &entry->thread.nodeName, HFSPLUS_MAX_STRLEN,
|
||||
str->name, str->len);
|
||||
return 10 + be16_to_cpu(entry->thread.nodeName.length) * 2;
|
||||
}
|
||||
|
||||
/* Try to get a catalog entry for given catalog id */
|
||||
int hfsplus_find_cat(struct super_block *sb, u32 cnid,
|
||||
struct hfs_find_data *fd)
|
||||
{
|
||||
hfsplus_cat_entry tmp;
|
||||
int err;
|
||||
u16 type;
|
||||
|
||||
hfsplus_cat_build_key(sb, fd->search_key, cnid, NULL);
|
||||
err = hfs_brec_read(fd, &tmp, sizeof(hfsplus_cat_entry));
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
type = be16_to_cpu(tmp.type);
|
||||
if (type != HFSPLUS_FOLDER_THREAD && type != HFSPLUS_FILE_THREAD) {
|
||||
pr_err("found bad thread record in catalog\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (be16_to_cpu(tmp.thread.nodeName.length) > 255) {
|
||||
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, 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)
|
||||
{
|
||||
struct super_block *sb = dir->i_sb;
|
||||
struct hfs_find_data fd;
|
||||
hfsplus_cat_entry entry;
|
||||
int entry_size;
|
||||
int err;
|
||||
|
||||
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) ?
|
||||
HFSPLUS_FOLDER_THREAD : HFSPLUS_FILE_THREAD,
|
||||
dir->i_ino, str);
|
||||
err = hfs_brec_find(&fd, hfs_find_rec_by_key);
|
||||
if (err != -ENOENT) {
|
||||
if (!err)
|
||||
err = -EEXIST;
|
||||
goto err2;
|
||||
}
|
||||
err = hfs_brec_insert(&fd, &entry, entry_size);
|
||||
if (err)
|
||||
goto err2;
|
||||
|
||||
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, hfs_find_rec_by_key);
|
||||
if (err != -ENOENT) {
|
||||
/* panic? */
|
||||
if (!err)
|
||||
err = -EEXIST;
|
||||
goto err1;
|
||||
}
|
||||
err = hfs_brec_insert(&fd, &entry, entry_size);
|
||||
if (err)
|
||||
goto err1;
|
||||
|
||||
dir->i_size++;
|
||||
if (S_ISDIR(inode->i_mode))
|
||||
hfsplus_subfolders_inc(dir);
|
||||
dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC;
|
||||
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, hfs_find_rec_by_key))
|
||||
hfs_brec_remove(&fd);
|
||||
err2:
|
||||
hfs_find_exit(&fd);
|
||||
return err;
|
||||
}
|
||||
|
||||
int hfsplus_delete_cat(u32 cnid, struct inode *dir, struct qstr *str)
|
||||
{
|
||||
struct super_block *sb = dir->i_sb;
|
||||
struct hfs_find_data fd;
|
||||
struct hfsplus_fork_raw fork;
|
||||
struct list_head *pos;
|
||||
int err, off;
|
||||
u16 type;
|
||||
|
||||
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, hfs_find_rec_by_key);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
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);
|
||||
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);
|
||||
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, hfs_find_rec_by_key);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
type = hfs_bnode_read_u16(fd.bnode, fd.entryoffset);
|
||||
if (type == HFSPLUS_FILE) {
|
||||
#if 0
|
||||
off = fd.entryoffset + offsetof(hfsplus_cat_file, data_fork);
|
||||
hfs_bnode_read(fd.bnode, &fork, off, sizeof(fork));
|
||||
hfsplus_free_fork(sb, cnid, &fork, HFSPLUS_TYPE_DATA);
|
||||
#endif
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
list_for_each(pos, &HFSPLUS_I(dir)->open_dir_list) {
|
||||
struct hfsplus_readdir_data *rd =
|
||||
list_entry(pos, struct hfsplus_readdir_data, list);
|
||||
if (fd.tree->keycmp(fd.search_key, (void *)&rd->key) < 0)
|
||||
rd->file->f_pos--;
|
||||
}
|
||||
|
||||
err = hfs_brec_remove(&fd);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
hfsplus_cat_build_key(sb, fd.search_key, cnid, NULL);
|
||||
err = hfs_brec_find(&fd, hfs_find_rec_by_key);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = hfs_brec_remove(&fd);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
dir->i_size--;
|
||||
if (type == HFSPLUS_FOLDER)
|
||||
hfsplus_subfolders_dec(dir);
|
||||
dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC;
|
||||
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);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int hfsplus_rename_cat(u32 cnid,
|
||||
struct inode *src_dir, struct qstr *src_name,
|
||||
struct inode *dst_dir, struct qstr *dst_name)
|
||||
{
|
||||
struct super_block *sb = src_dir->i_sb;
|
||||
struct hfs_find_data src_fd, dst_fd;
|
||||
hfsplus_cat_entry entry;
|
||||
int entry_size, type;
|
||||
int err;
|
||||
|
||||
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);
|
||||
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, 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, hfs_find_rec_by_key);
|
||||
if (err != -ENOENT) {
|
||||
if (!err)
|
||||
err = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = hfs_brec_insert(&dst_fd, &entry, src_fd.entrylength);
|
||||
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;
|
||||
|
||||
/* 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, 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;
|
||||
|
||||
/* remove old thread entry */
|
||||
hfsplus_cat_build_key(sb, src_fd.search_key, cnid, NULL);
|
||||
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);
|
||||
err = hfs_brec_remove(&src_fd);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/* 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, 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);
|
||||
return err;
|
||||
}
|
571
fs/hfsplus/dir.c
Normal file
571
fs/hfsplus/dir.c
Normal file
|
@ -0,0 +1,571 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/dir.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* Handling of directories
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#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)
|
||||
{
|
||||
dentry->d_fsdata = (void *)(unsigned long)cnid;
|
||||
d_instantiate(dentry, inode);
|
||||
}
|
||||
|
||||
/* Find the entry inside dir named dentry->d_name */
|
||||
static struct dentry *hfsplus_lookup(struct inode *dir, struct dentry *dentry,
|
||||
unsigned int flags)
|
||||
{
|
||||
struct inode *inode = NULL;
|
||||
struct hfs_find_data fd;
|
||||
struct super_block *sb;
|
||||
hfsplus_cat_entry entry;
|
||||
int err;
|
||||
u32 cnid, linkid = 0;
|
||||
u16 type;
|
||||
|
||||
sb = dir->i_sb;
|
||||
|
||||
dentry->d_fsdata = NULL;
|
||||
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));
|
||||
if (err) {
|
||||
if (err == -ENOENT) {
|
||||
hfs_find_exit(&fd);
|
||||
/* No such entry */
|
||||
inode = NULL;
|
||||
goto out;
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
type = be16_to_cpu(entry.type);
|
||||
if (type == HFSPLUS_FOLDER) {
|
||||
if (fd.entrylength < sizeof(struct hfsplus_cat_folder)) {
|
||||
err = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
cnid = be32_to_cpu(entry.folder.id);
|
||||
dentry->d_fsdata = (void *)(unsigned long)cnid;
|
||||
} else if (type == HFSPLUS_FILE) {
|
||||
if (fd.entrylength < sizeof(struct hfsplus_cat_file)) {
|
||||
err = -EIO;
|
||||
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) {
|
||||
struct qstr str;
|
||||
char name[32];
|
||||
|
||||
if (dentry->d_fsdata) {
|
||||
/*
|
||||
* We found a link pointing to another link,
|
||||
* so ignore it and treat it as regular file.
|
||||
*/
|
||||
cnid = (unsigned long)dentry->d_fsdata;
|
||||
linkid = 0;
|
||||
} else {
|
||||
dentry->d_fsdata = (void *)(unsigned long)cnid;
|
||||
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);
|
||||
goto again;
|
||||
}
|
||||
} else if (!dentry->d_fsdata)
|
||||
dentry->d_fsdata = (void *)(unsigned long)cnid;
|
||||
} else {
|
||||
pr_err("invalid catalog entry type in lookup\n");
|
||||
err = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
hfs_find_exit(&fd);
|
||||
inode = hfsplus_iget(dir->i_sb, cnid);
|
||||
if (IS_ERR(inode))
|
||||
return ERR_CAST(inode);
|
||||
if (S_ISREG(inode->i_mode))
|
||||
HFSPLUS_I(inode)->linkid = linkid;
|
||||
out:
|
||||
d_add(dentry, inode);
|
||||
return NULL;
|
||||
fail:
|
||||
hfs_find_exit(&fd);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
static int hfsplus_readdir(struct file *file, struct dir_context *ctx)
|
||||
{
|
||||
struct inode *inode = file_inode(file);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
int len, err;
|
||||
char *strbuf;
|
||||
hfsplus_cat_entry entry;
|
||||
struct hfs_find_data fd;
|
||||
struct hfsplus_readdir_data *rd;
|
||||
u16 type;
|
||||
|
||||
if (file->f_pos >= inode->i_size)
|
||||
return 0;
|
||||
|
||||
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, hfs_find_rec_by_key);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
if (ctx->pos == 0) {
|
||||
/* This is completely artificial... */
|
||||
if (!dir_emit_dot(file, ctx))
|
||||
goto out;
|
||||
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) {
|
||||
pr_err("bad catalog folder thread\n");
|
||||
err = -EIO;
|
||||
goto out;
|
||||
}
|
||||
if (fd.entrylength < HFSPLUS_MIN_THREAD_SZ) {
|
||||
pr_err("truncated catalog thread\n");
|
||||
err = -EIO;
|
||||
goto out;
|
||||
}
|
||||
if (!dir_emit(ctx, "..", 2,
|
||||
be32_to_cpu(entry.thread.parentID), DT_DIR))
|
||||
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) {
|
||||
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);
|
||||
type = be16_to_cpu(entry.type);
|
||||
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)) {
|
||||
pr_err("small dir entry\n");
|
||||
err = -EIO;
|
||||
goto out;
|
||||
}
|
||||
if (HFSPLUS_SB(sb)->hidden_dir &&
|
||||
HFSPLUS_SB(sb)->hidden_dir->i_ino ==
|
||||
be32_to_cpu(entry.folder.id))
|
||||
goto next;
|
||||
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)) {
|
||||
pr_err("small file entry\n");
|
||||
err = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
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 {
|
||||
pr_err("bad catalog entry type\n");
|
||||
err = -EIO;
|
||||
goto out;
|
||||
}
|
||||
next:
|
||||
ctx->pos++;
|
||||
if (ctx->pos >= inode->i_size)
|
||||
goto out;
|
||||
err = hfs_brec_goto(&fd, 1);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
rd = file->private_data;
|
||||
if (!rd) {
|
||||
rd = kmalloc(sizeof(struct hfsplus_readdir_data), GFP_KERNEL);
|
||||
if (!rd) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
static int hfsplus_dir_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct hfsplus_readdir_data *rd = file->private_data;
|
||||
if (rd) {
|
||||
mutex_lock(&inode->i_mutex);
|
||||
list_del(&rd->list);
|
||||
mutex_unlock(&inode->i_mutex);
|
||||
kfree(rd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,
|
||||
struct dentry *dst_dentry)
|
||||
{
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(dst_dir->i_sb);
|
||||
struct inode *inode = src_dentry->d_inode;
|
||||
struct inode *src_dir = src_dentry->d_parent->d_inode;
|
||||
struct qstr str;
|
||||
char name[32];
|
||||
u32 cnid, id;
|
||||
int res;
|
||||
|
||||
if (HFSPLUS_IS_RSRC(inode))
|
||||
return -EPERM;
|
||||
if (!S_ISREG(inode->i_mode))
|
||||
return -EPERM;
|
||||
|
||||
mutex_lock(&sbi->vh_mutex);
|
||||
if (inode->i_ino == (u32)(unsigned long)src_dentry->d_fsdata) {
|
||||
for (;;) {
|
||||
get_random_bytes(&id, sizeof(cnid));
|
||||
id &= 0x3fffffff;
|
||||
str.name = name;
|
||||
str.len = sprintf(name, "iNode%d", id);
|
||||
res = hfsplus_rename_cat(inode->i_ino,
|
||||
src_dir, &src_dentry->d_name,
|
||||
sbi->hidden_dir, &str);
|
||||
if (!res)
|
||||
break;
|
||||
if (res != -EEXIST)
|
||||
goto out;
|
||||
}
|
||||
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);
|
||||
if (res)
|
||||
/* panic? */
|
||||
goto out;
|
||||
sbi->file_count++;
|
||||
}
|
||||
cnid = sbi->next_cnid++;
|
||||
res = hfsplus_create_cat(cnid, dst_dir, &dst_dentry->d_name, inode);
|
||||
if (res)
|
||||
goto out;
|
||||
|
||||
inc_nlink(inode);
|
||||
hfsplus_instantiate(dst_dentry, inode, cnid);
|
||||
ihold(inode);
|
||||
inode->i_ctime = CURRENT_TIME_SEC;
|
||||
mark_inode_dirty(inode);
|
||||
sbi->file_count++;
|
||||
hfsplus_mark_mdb_dirty(dst_dir->i_sb);
|
||||
out:
|
||||
mutex_unlock(&sbi->vh_mutex);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int hfsplus_unlink(struct inode *dir, struct dentry *dentry)
|
||||
{
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
|
||||
struct inode *inode = dentry->d_inode;
|
||||
struct qstr str;
|
||||
char name[32];
|
||||
u32 cnid;
|
||||
int res;
|
||||
|
||||
if (HFSPLUS_IS_RSRC(inode))
|
||||
return -EPERM;
|
||||
|
||||
mutex_lock(&sbi->vh_mutex);
|
||||
cnid = (u32)(unsigned long)dentry->d_fsdata;
|
||||
if (inode->i_ino == cnid &&
|
||||
atomic_read(&HFSPLUS_I(inode)->opencnt)) {
|
||||
str.name = name;
|
||||
str.len = sprintf(name, "temp%lu", inode->i_ino);
|
||||
res = hfsplus_rename_cat(inode->i_ino,
|
||||
dir, &dentry->d_name,
|
||||
sbi->hidden_dir, &str);
|
||||
if (!res) {
|
||||
inode->i_flags |= S_DEAD;
|
||||
drop_nlink(inode);
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
res = hfsplus_delete_cat(cnid, dir, &dentry->d_name);
|
||||
if (res)
|
||||
goto out;
|
||||
|
||||
if (inode->i_nlink > 0)
|
||||
drop_nlink(inode);
|
||||
if (inode->i_ino == cnid)
|
||||
clear_nlink(inode);
|
||||
if (!inode->i_nlink) {
|
||||
if (inode->i_ino != cnid) {
|
||||
sbi->file_count--;
|
||||
if (!atomic_read(&HFSPLUS_I(inode)->opencnt)) {
|
||||
res = hfsplus_delete_cat(inode->i_ino,
|
||||
sbi->hidden_dir,
|
||||
NULL);
|
||||
if (!res)
|
||||
hfsplus_delete_inode(inode);
|
||||
} else
|
||||
inode->i_flags |= S_DEAD;
|
||||
} else
|
||||
hfsplus_delete_inode(inode);
|
||||
} else
|
||||
sbi->file_count--;
|
||||
inode->i_ctime = CURRENT_TIME_SEC;
|
||||
mark_inode_dirty(inode);
|
||||
out:
|
||||
mutex_unlock(&sbi->vh_mutex);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int hfsplus_rmdir(struct inode *dir, struct dentry *dentry)
|
||||
{
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
|
||||
struct inode *inode = dentry->d_inode;
|
||||
int res;
|
||||
|
||||
if (inode->i_size != 2)
|
||||
return -ENOTEMPTY;
|
||||
|
||||
mutex_lock(&sbi->vh_mutex);
|
||||
res = hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name);
|
||||
if (res)
|
||||
goto out;
|
||||
clear_nlink(inode);
|
||||
inode->i_ctime = CURRENT_TIME_SEC;
|
||||
hfsplus_delete_inode(inode);
|
||||
mark_inode_dirty(inode);
|
||||
out:
|
||||
mutex_unlock(&sbi->vh_mutex);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int hfsplus_symlink(struct inode *dir, struct dentry *dentry,
|
||||
const char *symname)
|
||||
{
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
|
||||
struct inode *inode;
|
||||
int res = -ENOSPC;
|
||||
|
||||
mutex_lock(&sbi->vh_mutex);
|
||||
inode = hfsplus_new_inode(dir->i_sb, S_IFLNK | S_IRWXUGO);
|
||||
if (!inode)
|
||||
goto out;
|
||||
|
||||
res = page_symlink(inode, symname, strlen(symname) + 1);
|
||||
if (res)
|
||||
goto out_err;
|
||||
|
||||
res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode);
|
||||
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:
|
||||
clear_nlink(inode);
|
||||
hfsplus_delete_inode(inode);
|
||||
iput(inode);
|
||||
out:
|
||||
mutex_unlock(&sbi->vh_mutex);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int hfsplus_mknod(struct inode *dir, struct dentry *dentry,
|
||||
umode_t mode, dev_t rdev)
|
||||
{
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
|
||||
struct inode *inode;
|
||||
int res = -ENOSPC;
|
||||
|
||||
mutex_lock(&sbi->vh_mutex);
|
||||
inode = hfsplus_new_inode(dir->i_sb, mode);
|
||||
if (!inode)
|
||||
goto out;
|
||||
|
||||
if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode))
|
||||
init_special_inode(inode, mode, rdev);
|
||||
|
||||
res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode);
|
||||
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, umode_t mode,
|
||||
bool excl)
|
||||
{
|
||||
return hfsplus_mknod(dir, dentry, mode, 0);
|
||||
}
|
||||
|
||||
static int hfsplus_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
|
||||
{
|
||||
return hfsplus_mknod(dir, dentry, mode | S_IFDIR, 0);
|
||||
}
|
||||
|
||||
static int hfsplus_rename(struct inode *old_dir, struct dentry *old_dentry,
|
||||
struct inode *new_dir, struct dentry *new_dentry)
|
||||
{
|
||||
int res;
|
||||
|
||||
/* Unlink destination if it already exists */
|
||||
if (new_dentry->d_inode) {
|
||||
if (S_ISDIR(new_dentry->d_inode->i_mode))
|
||||
res = hfsplus_rmdir(new_dir, new_dentry);
|
||||
else
|
||||
res = hfsplus_unlink(new_dir, new_dentry);
|
||||
if (res)
|
||||
return res;
|
||||
}
|
||||
|
||||
res = hfsplus_rename_cat((u32)(unsigned long)old_dentry->d_fsdata,
|
||||
old_dir, &old_dentry->d_name,
|
||||
new_dir, &new_dentry->d_name);
|
||||
if (!res)
|
||||
new_dentry->d_fsdata = old_dentry->d_fsdata;
|
||||
return res;
|
||||
}
|
||||
|
||||
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,
|
||||
.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,
|
||||
.iterate = hfsplus_readdir,
|
||||
.unlocked_ioctl = hfsplus_ioctl,
|
||||
.llseek = generic_file_llseek,
|
||||
.release = hfsplus_dir_release,
|
||||
};
|
611
fs/hfsplus/extents.c
Normal file
611
fs/hfsplus/extents.c
Normal file
|
@ -0,0 +1,611 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/extents.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* Handling of Extents both in catalog and extents overflow trees
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/pagemap.h>
|
||||
|
||||
#include "hfsplus_fs.h"
|
||||
#include "hfsplus_raw.h"
|
||||
|
||||
/* Compare two extents keys, returns 0 on same, pos/neg for difference */
|
||||
int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1,
|
||||
const hfsplus_btree_key *k2)
|
||||
{
|
||||
__be32 k1id, k2id;
|
||||
__be32 k1s, k2s;
|
||||
|
||||
k1id = k1->ext.cnid;
|
||||
k2id = k2->ext.cnid;
|
||||
if (k1id != k2id)
|
||||
return be32_to_cpu(k1id) < be32_to_cpu(k2id) ? -1 : 1;
|
||||
|
||||
if (k1->ext.fork_type != k2->ext.fork_type)
|
||||
return k1->ext.fork_type < k2->ext.fork_type ? -1 : 1;
|
||||
|
||||
k1s = k1->ext.start_block;
|
||||
k2s = k2->ext.start_block;
|
||||
if (k1s == k2s)
|
||||
return 0;
|
||||
return be32_to_cpu(k1s) < be32_to_cpu(k2s) ? -1 : 1;
|
||||
}
|
||||
|
||||
static void hfsplus_ext_build_key(hfsplus_btree_key *key, u32 cnid,
|
||||
u32 block, u8 type)
|
||||
{
|
||||
key->key_len = cpu_to_be16(HFSPLUS_EXT_KEYLEN - 2);
|
||||
key->ext.cnid = cpu_to_be32(cnid);
|
||||
key->ext.start_block = cpu_to_be32(block);
|
||||
key->ext.fork_type = type;
|
||||
key->ext.pad = 0;
|
||||
}
|
||||
|
||||
static u32 hfsplus_ext_find_block(struct hfsplus_extent *ext, u32 off)
|
||||
{
|
||||
int i;
|
||||
u32 count;
|
||||
|
||||
for (i = 0; i < 8; ext++, i++) {
|
||||
count = be32_to_cpu(ext->block_count);
|
||||
if (off < count)
|
||||
return be32_to_cpu(ext->start_block) + off;
|
||||
off -= count;
|
||||
}
|
||||
/* panic? */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hfsplus_ext_block_count(struct hfsplus_extent *ext)
|
||||
{
|
||||
int i;
|
||||
u32 count = 0;
|
||||
|
||||
for (i = 0; i < 8; ext++, i++)
|
||||
count += be32_to_cpu(ext->block_count);
|
||||
return count;
|
||||
}
|
||||
|
||||
static u32 hfsplus_ext_lastblock(struct hfsplus_extent *ext)
|
||||
{
|
||||
int i;
|
||||
|
||||
ext += 7;
|
||||
for (i = 0; i < 7; ext--, i++)
|
||||
if (ext->block_count)
|
||||
break;
|
||||
return be32_to_cpu(ext->start_block) + be32_to_cpu(ext->block_count);
|
||||
}
|
||||
|
||||
static int __hfsplus_ext_write_extent(struct inode *inode,
|
||||
struct hfs_find_data *fd)
|
||||
{
|
||||
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
|
||||
int res;
|
||||
|
||||
WARN_ON(!mutex_is_locked(&hip->extents_lock));
|
||||
|
||||
hfsplus_ext_build_key(fd->search_key, inode->i_ino, hip->cached_start,
|
||||
HFSPLUS_IS_RSRC(inode) ?
|
||||
HFSPLUS_TYPE_RSRC : HFSPLUS_TYPE_DATA);
|
||||
|
||||
res = hfs_brec_find(fd, hfs_find_rec_by_key);
|
||||
if (hip->extent_state & HFSPLUS_EXT_NEW) {
|
||||
if (res != -ENOENT)
|
||||
return res;
|
||||
hfs_brec_insert(fd, hip->cached_extents,
|
||||
sizeof(hfsplus_extent_rec));
|
||||
hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW);
|
||||
} else {
|
||||
if (res)
|
||||
return res;
|
||||
hfs_bnode_write(fd->bnode, hip->cached_extents,
|
||||
fd->entryoffset, fd->entrylength);
|
||||
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 int hfsplus_ext_write_extent_locked(struct inode *inode)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
if (HFSPLUS_I(inode)->extent_state & HFSPLUS_EXT_DIRTY) {
|
||||
struct hfs_find_data 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;
|
||||
}
|
||||
|
||||
int hfsplus_ext_write_extent(struct inode *inode)
|
||||
{
|
||||
int res;
|
||||
|
||||
mutex_lock(&HFSPLUS_I(inode)->extents_lock);
|
||||
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,
|
||||
struct hfsplus_extent *extent,
|
||||
u32 cnid, u32 block, u8 type)
|
||||
{
|
||||
int res;
|
||||
|
||||
hfsplus_ext_build_key(fd->search_key, cnid, block, type);
|
||||
fd->key->ext.cnid = 0;
|
||||
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 ||
|
||||
fd->key->ext.fork_type != fd->search_key->ext.fork_type)
|
||||
return -ENOENT;
|
||||
if (fd->entrylength != sizeof(hfsplus_extent_rec))
|
||||
return -EIO;
|
||||
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)
|
||||
{
|
||||
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
|
||||
int res;
|
||||
|
||||
WARN_ON(!mutex_is_locked(&hip->extents_lock));
|
||||
|
||||
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) ?
|
||||
HFSPLUS_TYPE_RSRC :
|
||||
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);
|
||||
} else {
|
||||
hip->cached_start = hip->cached_blocks = 0;
|
||||
hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int hfsplus_ext_read_extent(struct inode *inode, u32 block)
|
||||
{
|
||||
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
|
||||
struct hfs_find_data fd;
|
||||
int res;
|
||||
|
||||
if (block >= hip->cached_start &&
|
||||
block < hip->cached_start + hip->cached_blocks)
|
||||
return 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* Get a block at iblock for inode, possibly allocating if create */
|
||||
int hfsplus_get_block(struct inode *inode, sector_t iblock,
|
||||
struct buffer_head *bh_result, int create)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
|
||||
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
|
||||
int res = -EIO;
|
||||
u32 ablock, dblock, mask;
|
||||
sector_t sector;
|
||||
int was_dirty = 0;
|
||||
|
||||
/* Convert inode block to disk allocation block */
|
||||
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, false);
|
||||
if (res)
|
||||
return res;
|
||||
}
|
||||
} else
|
||||
create = 0;
|
||||
|
||||
if (ablock < hip->first_blocks) {
|
||||
dblock = hfsplus_ext_find_block(hip->first_extents, ablock);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (inode->i_ino == HFSPLUS_EXT_CNID)
|
||||
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) {
|
||||
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:
|
||||
hfs_dbg(EXTENT, "get_block(%lu): %llu - %u\n",
|
||||
inode->i_ino, (long long)iblock, dblock);
|
||||
|
||||
mask = (1 << sbi->fs_shift) - 1;
|
||||
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);
|
||||
}
|
||||
if (create || was_dirty)
|
||||
mark_inode_dirty(inode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hfsplus_dump_extent(struct hfsplus_extent *extent)
|
||||
{
|
||||
int i;
|
||||
|
||||
hfs_dbg(EXTENT, " ");
|
||||
for (i = 0; i < 8; i++)
|
||||
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,
|
||||
u32 alloc_block, u32 block_count)
|
||||
{
|
||||
u32 count, start;
|
||||
int i;
|
||||
|
||||
hfsplus_dump_extent(extent);
|
||||
for (i = 0; i < 8; extent++, i++) {
|
||||
count = be32_to_cpu(extent->block_count);
|
||||
if (offset == count) {
|
||||
start = be32_to_cpu(extent->start_block);
|
||||
if (alloc_block != start + count) {
|
||||
if (++i >= 8)
|
||||
return -ENOSPC;
|
||||
extent++;
|
||||
extent->start_block = cpu_to_be32(alloc_block);
|
||||
} else
|
||||
block_count += count;
|
||||
extent->block_count = cpu_to_be32(block_count);
|
||||
return 0;
|
||||
} else if (offset < count)
|
||||
break;
|
||||
offset -= count;
|
||||
}
|
||||
/* panic? */
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int hfsplus_free_extents(struct super_block *sb,
|
||||
struct hfsplus_extent *extent,
|
||||
u32 offset, u32 block_nr)
|
||||
{
|
||||
u32 count, start;
|
||||
int i;
|
||||
int err = 0;
|
||||
|
||||
hfsplus_dump_extent(extent);
|
||||
for (i = 0; i < 8; extent++, i++) {
|
||||
count = be32_to_cpu(extent->block_count);
|
||||
if (offset == count)
|
||||
goto found;
|
||||
else if (offset < count)
|
||||
break;
|
||||
offset -= count;
|
||||
}
|
||||
/* panic? */
|
||||
return -EIO;
|
||||
found:
|
||||
for (;;) {
|
||||
start = be32_to_cpu(extent->start_block);
|
||||
if (count <= block_nr) {
|
||||
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;
|
||||
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) {
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
struct hfs_find_data fd;
|
||||
hfsplus_extent_rec ext_entry;
|
||||
u32 total_blocks, blocks, start;
|
||||
int res, i;
|
||||
|
||||
total_blocks = be32_to_cpu(fork->total_blocks);
|
||||
if (!total_blocks)
|
||||
return 0;
|
||||
|
||||
blocks = 0;
|
||||
for (i = 0; i < 8; i++)
|
||||
blocks += be32_to_cpu(fork->extents[i].block_count);
|
||||
|
||||
res = hfsplus_free_extents(sb, fork->extents, blocks, blocks);
|
||||
if (res)
|
||||
return res;
|
||||
if (total_blocks == blocks)
|
||||
return 0;
|
||||
|
||||
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);
|
||||
if (res)
|
||||
break;
|
||||
start = be32_to_cpu(fd.key->ext.start_block);
|
||||
hfsplus_free_extents(sb, ext_entry,
|
||||
total_blocks - start,
|
||||
total_blocks);
|
||||
hfs_brec_remove(&fd);
|
||||
total_blocks = start;
|
||||
} while (total_blocks > blocks);
|
||||
hfs_find_exit(&fd);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int hfsplus_file_extend(struct inode *inode, bool zeroout)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
|
||||
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
|
||||
u32 start, len, goal;
|
||||
int res;
|
||||
|
||||
if (sbi->alloc_file->i_size * 8 <
|
||||
sbi->total_blocks - sbi->free_blocks + 8) {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
mutex_lock(&hip->extents_lock);
|
||||
if (hip->alloc_blocks == hip->first_blocks)
|
||||
goal = hfsplus_ext_lastblock(hip->first_extents);
|
||||
else {
|
||||
res = hfsplus_ext_read_extent(inode, hip->alloc_blocks);
|
||||
if (res)
|
||||
goto out;
|
||||
goal = hfsplus_ext_lastblock(hip->cached_extents);
|
||||
}
|
||||
|
||||
len = hip->clump_blocks;
|
||||
start = hfsplus_block_allocate(sb, sbi->total_blocks, goal, &len);
|
||||
if (start >= sbi->total_blocks) {
|
||||
start = hfsplus_block_allocate(sb, goal, 0, &len);
|
||||
if (start >= goal) {
|
||||
res = -ENOSPC;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
res = 0;
|
||||
} else {
|
||||
/* try to append to extents in inode */
|
||||
res = hfsplus_add_extent(hip->first_extents,
|
||||
hip->alloc_blocks,
|
||||
start, len);
|
||||
if (res == -ENOSPC)
|
||||
goto insert_extent;
|
||||
}
|
||||
if (!res) {
|
||||
hfsplus_dump_extent(hip->first_extents);
|
||||
hip->first_blocks += len;
|
||||
}
|
||||
} else {
|
||||
res = hfsplus_add_extent(hip->cached_extents,
|
||||
hip->alloc_blocks - hip->cached_start,
|
||||
start, len);
|
||||
if (!res) {
|
||||
hfsplus_dump_extent(hip->cached_extents);
|
||||
hip->extent_state |= HFSPLUS_EXT_DIRTY;
|
||||
hip->cached_blocks += len;
|
||||
} else if (res == -ENOSPC)
|
||||
goto insert_extent;
|
||||
}
|
||||
out:
|
||||
if (!res) {
|
||||
hip->alloc_blocks += len;
|
||||
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:
|
||||
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->extent_state |= HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW;
|
||||
hip->cached_start = hip->alloc_blocks;
|
||||
hip->cached_blocks = len;
|
||||
|
||||
res = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
void hfsplus_file_truncate(struct inode *inode)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
|
||||
struct hfs_find_data fd;
|
||||
u32 alloc_cnt, blk_cnt, start;
|
||||
int res;
|
||||
|
||||
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;
|
||||
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);
|
||||
if (res < 0)
|
||||
return;
|
||||
mark_inode_dirty(inode);
|
||||
return;
|
||||
} else if (inode->i_size == hip->phys_size)
|
||||
return;
|
||||
|
||||
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_unlock;
|
||||
|
||||
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,
|
||||
alloc_cnt, alloc_cnt - blk_cnt);
|
||||
hfsplus_dump_extent(hip->first_extents);
|
||||
hip->first_blocks = blk_cnt;
|
||||
break;
|
||||
}
|
||||
res = __hfsplus_ext_cache_extent(&fd, inode, alloc_cnt);
|
||||
if (res)
|
||||
break;
|
||||
start = hip->cached_start;
|
||||
hfsplus_free_extents(sb, hip->cached_extents,
|
||||
alloc_cnt - start, alloc_cnt - blk_cnt);
|
||||
hfsplus_dump_extent(hip->cached_extents);
|
||||
if (blk_cnt > start) {
|
||||
hip->extent_state |= HFSPLUS_EXT_DIRTY;
|
||||
break;
|
||||
}
|
||||
alloc_cnt = start;
|
||||
hip->cached_start = hip->cached_blocks = 0;
|
||||
hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW);
|
||||
hfs_brec_remove(&fd);
|
||||
}
|
||||
hfs_find_exit(&fd);
|
||||
|
||||
hip->alloc_blocks = blk_cnt;
|
||||
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;
|
||||
inode_set_bytes(inode, hip->fs_blocks << sb->s_blocksize_bits);
|
||||
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ALLOC_DIRTY);
|
||||
}
|
538
fs/hfsplus/hfsplus_fs.h
Normal file
538
fs/hfsplus/hfsplus_fs.h
Normal file
|
@ -0,0 +1,538 @@
|
|||
/*
|
||||
* linux/include/linux/hfsplus_fs.h
|
||||
*
|
||||
* Copyright (C) 1999
|
||||
* Brad Boyer (flar@pants.nu)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#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
|
||||
#define DBG_BNODE_MOD 0x00000002
|
||||
#define DBG_CAT_MOD 0x00000004
|
||||
#define DBG_INODE 0x00000008
|
||||
#define DBG_SUPER 0x00000010
|
||||
#define DBG_EXTENT 0x00000020
|
||||
#define DBG_BITMAP 0x00000040
|
||||
#define DBG_ATTR_MOD 0x00000080
|
||||
#define DBG_ACL_MOD 0x00000100
|
||||
|
||||
#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 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 /* '????' */
|
||||
|
||||
#define HFSPLUS_TYPE_DATA 0x00
|
||||
#define HFSPLUS_TYPE_RSRC 0xFF
|
||||
|
||||
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;
|
||||
struct inode *inode;
|
||||
btree_keycmp keycmp;
|
||||
|
||||
u32 cnid;
|
||||
u32 root;
|
||||
u32 leaf_count;
|
||||
u32 leaf_head;
|
||||
u32 leaf_tail;
|
||||
u32 node_count;
|
||||
u32 free_nodes;
|
||||
u32 attributes;
|
||||
|
||||
unsigned int node_size;
|
||||
unsigned int node_size_shift;
|
||||
unsigned int max_key_len;
|
||||
unsigned int depth;
|
||||
|
||||
struct mutex tree_lock;
|
||||
|
||||
unsigned int pages_per_bnode;
|
||||
spinlock_t hash_lock;
|
||||
struct hfs_bnode *node_hash[NODE_HASH_SIZE];
|
||||
int node_hash_cnt;
|
||||
};
|
||||
|
||||
struct page;
|
||||
|
||||
/* An HFS+ BTree node in memory */
|
||||
struct hfs_bnode {
|
||||
struct hfs_btree *tree;
|
||||
|
||||
u32 prev;
|
||||
u32 this;
|
||||
u32 next;
|
||||
u32 parent;
|
||||
|
||||
u16 num_recs;
|
||||
u8 type;
|
||||
u8 height;
|
||||
|
||||
struct hfs_bnode *next_hash;
|
||||
unsigned long flags;
|
||||
wait_queue_head_t lock_wq;
|
||||
atomic_t refcnt;
|
||||
unsigned int page_offset;
|
||||
struct page *page[0];
|
||||
};
|
||||
|
||||
#define HFS_BNODE_LOCK 0
|
||||
#define HFS_BNODE_ERROR 1
|
||||
#define HFS_BNODE_NEW 2
|
||||
#define HFS_BNODE_DIRTY 3
|
||||
#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)
|
||||
*/
|
||||
|
||||
struct hfsplus_vh;
|
||||
struct hfs_btree;
|
||||
|
||||
struct hfsplus_sb_info {
|
||||
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;
|
||||
sector_t part_start;
|
||||
sector_t sect_count;
|
||||
int fs_shift;
|
||||
|
||||
/* immutable data from the volume header */
|
||||
u32 alloc_blksz;
|
||||
int alloc_blksz_shift;
|
||||
u32 total_blocks;
|
||||
u32 data_clump_blocks, rsrc_clump_blocks;
|
||||
|
||||
/* mutable data from the volume header, protected by alloc_mutex */
|
||||
u32 free_blocks;
|
||||
struct mutex alloc_mutex;
|
||||
|
||||
/* mutable data from the volume header, protected by vh_mutex */
|
||||
u32 next_cnid;
|
||||
u32 file_count;
|
||||
u32 folder_count;
|
||||
struct mutex vh_mutex;
|
||||
|
||||
/* Config options */
|
||||
u32 creator;
|
||||
u32 type;
|
||||
|
||||
umode_t umask;
|
||||
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
|
||||
#define HFSPLUS_SB_NODECOMPOSE 1
|
||||
#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 {
|
||||
atomic_t opencnt;
|
||||
|
||||
/*
|
||||
* Extent allocation information, protected by extents_lock.
|
||||
*/
|
||||
u32 first_blocks;
|
||||
u32 clump_blocks;
|
||||
u32 alloc_blocks;
|
||||
u32 cached_start;
|
||||
u32 cached_blocks;
|
||||
hfsplus_extent_rec first_extents;
|
||||
hfsplus_extent_rec cached_extents;
|
||||
unsigned int extent_state;
|
||||
struct mutex extents_lock;
|
||||
|
||||
/*
|
||||
* Immutable data.
|
||||
*/
|
||||
struct inode *rsrc_inode;
|
||||
__be32 create_date;
|
||||
|
||||
/*
|
||||
* Protected by sbi->vh_mutex.
|
||||
*/
|
||||
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_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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
hfsplus_btree_key *search_key;
|
||||
hfsplus_btree_key *key;
|
||||
/* filled by find */
|
||||
struct hfs_btree *tree;
|
||||
struct hfs_bnode *bnode;
|
||||
/* filled by findrec */
|
||||
int record;
|
||||
int keyoffset, keylength;
|
||||
int entryoffset, entrylength;
|
||||
};
|
||||
|
||||
struct hfsplus_readdir_data {
|
||||
struct list_head list;
|
||||
struct file *file;
|
||||
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
|
||||
#define hfs_bmap_alloc hfsplus_bmap_alloc
|
||||
#define hfs_bmap_free hfsplus_bmap_free
|
||||
#define hfs_bnode_read hfsplus_bnode_read
|
||||
#define hfs_bnode_read_u16 hfsplus_bnode_read_u16
|
||||
#define hfs_bnode_read_u8 hfsplus_bnode_read_u8
|
||||
#define hfs_bnode_read_key hfsplus_bnode_read_key
|
||||
#define hfs_bnode_write hfsplus_bnode_write
|
||||
#define hfs_bnode_write_u16 hfsplus_bnode_write_u16
|
||||
#define hfs_bnode_clear hfsplus_bnode_clear
|
||||
#define hfs_bnode_copy hfsplus_bnode_copy
|
||||
#define hfs_bnode_move hfsplus_bnode_move
|
||||
#define hfs_bnode_dump hfsplus_bnode_dump
|
||||
#define hfs_bnode_unlink hfsplus_bnode_unlink
|
||||
#define hfs_bnode_findhash hfsplus_bnode_findhash
|
||||
#define hfs_bnode_find hfsplus_bnode_find
|
||||
#define hfs_bnode_unhash hfsplus_bnode_unhash
|
||||
#define hfs_bnode_free hfsplus_bnode_free
|
||||
#define hfs_bnode_create hfsplus_bnode_create
|
||||
#define hfs_bnode_get hfsplus_bnode_get
|
||||
#define hfs_bnode_put hfsplus_bnode_put
|
||||
#define hfs_brec_lenoff hfsplus_brec_lenoff
|
||||
#define hfs_brec_keylen hfsplus_brec_keylen
|
||||
#define hfs_brec_insert hfsplus_brec_insert
|
||||
#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 __hfsplus_brec_find
|
||||
#define hfs_brec_find hfsplus_brec_find
|
||||
#define hfs_brec_read hfsplus_brec_read
|
||||
#define hfs_brec_goto hfsplus_brec_goto
|
||||
#define hfs_part_find hfsplus_part_find
|
||||
|
||||
/*
|
||||
* definitions for ext2 flag ioctls (linux really needs a generic
|
||||
* interface for this).
|
||||
*/
|
||||
|
||||
/* ext2 ioctls (EXT2_IOC_GETFLAGS and EXT2_IOC_SETFLAGS) to support
|
||||
* chattr/lsattr */
|
||||
#define HFSPLUS_IOC_EXT2_GETFLAGS FS_IOC_GETFLAGS
|
||||
#define HFSPLUS_IOC_EXT2_SETFLAGS FS_IOC_SETFLAGS
|
||||
|
||||
|
||||
/*
|
||||
* 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 *sb, u32 size, u32 offset,
|
||||
u32 *max);
|
||||
int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count);
|
||||
|
||||
/* btree.c */
|
||||
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 *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 *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 *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 *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 *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;
|
||||
|
||||
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);
|
||||
|
||||
/* options.c */
|
||||
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 *sb, unsigned long ino);
|
||||
void hfsplus_mark_mdb_dirty(struct super_block *sb);
|
||||
|
||||
/* tables.c */
|
||||
extern u16 hfsplus_case_fold_table[];
|
||||
extern u16 hfsplus_decompose_table[];
|
||||
extern u16 hfsplus_compose_table[];
|
||||
|
||||
/* unicode.c */
|
||||
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_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)
|
||||
#define __hfsp_ut2mt(t) (cpu_to_be32(t + 2082844800U))
|
||||
|
||||
/* compatibility */
|
||||
#define hfsp_mt2ut(t) (struct timespec){ .tv_sec = __hfsp_mt2ut(t) }
|
||||
#define hfsp_ut2mt(t) __hfsp_ut2mt((t).tv_sec)
|
||||
#define hfsp_now2mt() __hfsp_ut2mt(get_seconds())
|
||||
|
||||
#endif
|
407
fs/hfsplus/hfsplus_raw.h
Normal file
407
fs/hfsplus/hfsplus_raw.h
Normal file
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
* linux/include/linux/hfsplus_raw.h
|
||||
*
|
||||
* Copyright (C) 1999
|
||||
* Brad Boyer (flar@pants.nu)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* Format of structures on disk
|
||||
* Information taken from Apple Technote #1150 (HFS Plus Volume Format)
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_HFSPLUS_RAW_H
|
||||
#define _LINUX_HFSPLUS_RAW_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/* Some constants */
|
||||
#define HFSPLUS_SECTOR_SIZE 512
|
||||
#define HFSPLUS_SECTOR_SHIFT 9
|
||||
#define HFSPLUS_VOLHEAD_SECTOR 2
|
||||
#define HFSPLUS_VOLHEAD_SIG 0x482b
|
||||
#define HFSPLUS_VOLHEAD_SIGX 0x4858
|
||||
#define HFSPLUS_SUPER_MAGIC 0x482b
|
||||
#define HFSPLUS_MIN_VERSION 4
|
||||
#define HFSPLUS_CURRENT_VERSION 5
|
||||
|
||||
#define HFSP_WRAP_MAGIC 0x4244
|
||||
#define HFSP_WRAP_ATTRIB_SLOCK 0x8000
|
||||
#define HFSP_WRAP_ATTRIB_SPARED 0x0200
|
||||
|
||||
#define HFSP_WRAPOFF_SIG 0x00
|
||||
#define HFSP_WRAPOFF_ATTRIB 0x0A
|
||||
#define HFSP_WRAPOFF_ABLKSIZE 0x14
|
||||
#define HFSP_WRAPOFF_ABLKSTART 0x1C
|
||||
#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_HARDLINK_TYPE 0x686c6e6b /* 'hlnk' */
|
||||
#define HFSP_HFSPLUS_CREATOR 0x6866732b /* 'hfs+' */
|
||||
|
||||
#define HFSP_SYMLINK_TYPE 0x736c6e6b /* 'slnk' */
|
||||
#define HFSP_SYMLINK_CREATOR 0x72686170 /* 'rhap' */
|
||||
|
||||
#define HFSP_MOUNT_VERSION 0x482b4c78 /* 'H+Lx' */
|
||||
|
||||
/* Structures used on disk */
|
||||
|
||||
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[HFSPLUS_MAX_STRLEN];
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* 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 {
|
||||
__be32 owner;
|
||||
__be32 group;
|
||||
u8 rootflags;
|
||||
u8 userflags;
|
||||
__be16 mode;
|
||||
__be32 dev;
|
||||
} __packed;
|
||||
|
||||
#define HFSPLUS_FLG_NODUMP 0x01
|
||||
#define HFSPLUS_FLG_IMMUTABLE 0x02
|
||||
#define HFSPLUS_FLG_APPEND 0x04
|
||||
|
||||
/* A single contiguous area of a file */
|
||||
struct hfsplus_extent {
|
||||
__be32 start_block;
|
||||
__be32 block_count;
|
||||
} __packed;
|
||||
typedef struct hfsplus_extent hfsplus_extent_rec[8];
|
||||
|
||||
/* Information for a "Fork" in a file */
|
||||
struct hfsplus_fork_raw {
|
||||
__be64 total_size;
|
||||
__be32 clump_size;
|
||||
__be32 total_blocks;
|
||||
hfsplus_extent_rec extents;
|
||||
} __packed;
|
||||
|
||||
/* HFS+ Volume Header */
|
||||
struct hfsplus_vh {
|
||||
__be16 signature;
|
||||
__be16 version;
|
||||
__be32 attributes;
|
||||
__be32 last_mount_vers;
|
||||
u32 reserved;
|
||||
|
||||
__be32 create_date;
|
||||
__be32 modify_date;
|
||||
__be32 backup_date;
|
||||
__be32 checked_date;
|
||||
|
||||
__be32 file_count;
|
||||
__be32 folder_count;
|
||||
|
||||
__be32 blocksize;
|
||||
__be32 total_blocks;
|
||||
__be32 free_blocks;
|
||||
|
||||
__be32 next_alloc;
|
||||
__be32 rsrc_clump_sz;
|
||||
__be32 data_clump_sz;
|
||||
hfsplus_cnid next_cnid;
|
||||
|
||||
__be32 write_count;
|
||||
__be64 encodings_bmp;
|
||||
|
||||
u32 finder_info[8];
|
||||
|
||||
struct hfsplus_fork_raw alloc_file;
|
||||
struct hfsplus_fork_raw ext_file;
|
||||
struct hfsplus_fork_raw cat_file;
|
||||
struct hfsplus_fork_raw attr_file;
|
||||
struct hfsplus_fork_raw start_file;
|
||||
} __packed;
|
||||
|
||||
/* HFS+ volume attributes */
|
||||
#define HFSPLUS_VOL_UNMNT (1 << 8)
|
||||
#define HFSPLUS_VOL_SPARE_BLK (1 << 9)
|
||||
#define HFSPLUS_VOL_NOCACHE (1 << 10)
|
||||
#define HFSPLUS_VOL_INCNSTNT (1 << 11)
|
||||
#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 {
|
||||
__be32 next;
|
||||
__be32 prev;
|
||||
s8 type;
|
||||
u8 height;
|
||||
__be16 num_recs;
|
||||
u16 reserved;
|
||||
} __packed;
|
||||
|
||||
/* HFS+ BTree node types */
|
||||
#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 {
|
||||
__be16 depth;
|
||||
__be32 root;
|
||||
__be32 leaf_count;
|
||||
__be32 leaf_head;
|
||||
__be32 leaf_tail;
|
||||
__be16 node_size;
|
||||
__be16 max_key_len;
|
||||
__be32 node_count;
|
||||
__be32 free_nodes;
|
||||
u16 reserved1;
|
||||
__be32 clump_size;
|
||||
u8 btree_type;
|
||||
u8 key_type;
|
||||
__be32 attributes;
|
||||
u32 reserved3[16];
|
||||
} __packed;
|
||||
|
||||
/* BTree attributes */
|
||||
#define HFS_TREE_BIGKEYS 2
|
||||
#define HFS_TREE_VARIDXKEYS 4
|
||||
|
||||
/* 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 */
|
||||
#define HFSPLUS_ROOT_CNID 2 /* ROOT directory */
|
||||
#define HFSPLUS_EXT_CNID 3 /* EXTents B-tree */
|
||||
#define HFSPLUS_CAT_CNID 4 /* CATalog B-tree */
|
||||
#define HFSPLUS_BAD_CNID 5 /* BAD blocks file */
|
||||
#define HFSPLUS_ALLOC_CNID 6 /* ALLOCation file */
|
||||
#define HFSPLUS_START_CNID 7 /* STARTup file */
|
||||
#define HFSPLUS_ATTR_CNID 8 /* ATTRibutes file */
|
||||
#define HFSPLUS_EXCH_CNID 15 /* ExchangeFiles temp id */
|
||||
#define HFSPLUS_FIRSTUSER_CNID 16 /* first available user id */
|
||||
|
||||
/* btree key type */
|
||||
#define HFSPLUS_KEY_CASEFOLDING 0xCF /* case-insensitive */
|
||||
#define HFSPLUS_KEY_BINARY 0xBC /* case-sensitive */
|
||||
|
||||
/* HFS+ catalog entry key */
|
||||
struct hfsplus_cat_key {
|
||||
__be16 key_len;
|
||||
hfsplus_cnid parent;
|
||||
struct hfsplus_unistr name;
|
||||
} __packed;
|
||||
|
||||
#define HFSPLUS_CAT_KEYLEN (sizeof(struct hfsplus_cat_key))
|
||||
|
||||
/* Structs from hfs.h */
|
||||
struct hfsp_point {
|
||||
__be16 v;
|
||||
__be16 h;
|
||||
} __packed;
|
||||
|
||||
struct hfsp_rect {
|
||||
__be16 top;
|
||||
__be16 left;
|
||||
__be16 bottom;
|
||||
__be16 right;
|
||||
} __packed;
|
||||
|
||||
|
||||
/* HFS directory info (stolen from hfs.h */
|
||||
struct DInfo {
|
||||
struct hfsp_rect frRect;
|
||||
__be16 frFlags;
|
||||
struct hfsp_point frLocation;
|
||||
__be16 frView;
|
||||
} __packed;
|
||||
|
||||
struct DXInfo {
|
||||
struct hfsp_point frScroll;
|
||||
__be32 frOpenChain;
|
||||
__be16 frUnused;
|
||||
__be16 frComment;
|
||||
__be32 frPutAway;
|
||||
} __packed;
|
||||
|
||||
/* HFS+ folder data (part of an hfsplus_cat_entry) */
|
||||
struct hfsplus_cat_folder {
|
||||
__be16 type;
|
||||
__be16 flags;
|
||||
__be32 valence;
|
||||
hfsplus_cnid id;
|
||||
__be32 create_date;
|
||||
__be32 content_mod_date;
|
||||
__be32 attribute_mod_date;
|
||||
__be32 access_date;
|
||||
__be32 backup_date;
|
||||
struct hfsplus_perm permissions;
|
||||
struct DInfo user_info;
|
||||
struct DXInfo finder_info;
|
||||
__be32 text_encoding;
|
||||
__be32 subfolders; /* Subfolder count in HFSX. Reserved in HFS+. */
|
||||
} __packed;
|
||||
|
||||
/* HFS file info (stolen from hfs.h) */
|
||||
struct FInfo {
|
||||
__be32 fdType;
|
||||
__be32 fdCreator;
|
||||
__be16 fdFlags;
|
||||
struct hfsp_point fdLocation;
|
||||
__be16 fdFldr;
|
||||
} __packed;
|
||||
|
||||
struct FXInfo {
|
||||
__be16 fdIconID;
|
||||
u8 fdUnused[8];
|
||||
__be16 fdComment;
|
||||
__be32 fdPutAway;
|
||||
} __packed;
|
||||
|
||||
/* HFS+ file data (part of a cat_entry) */
|
||||
struct hfsplus_cat_file {
|
||||
__be16 type;
|
||||
__be16 flags;
|
||||
u32 reserved1;
|
||||
hfsplus_cnid id;
|
||||
__be32 create_date;
|
||||
__be32 content_mod_date;
|
||||
__be32 attribute_mod_date;
|
||||
__be32 access_date;
|
||||
__be32 backup_date;
|
||||
struct hfsplus_perm permissions;
|
||||
struct FInfo user_info;
|
||||
struct FXInfo finder_info;
|
||||
__be32 text_encoding;
|
||||
u32 reserved2;
|
||||
|
||||
struct hfsplus_fork_raw data_fork;
|
||||
struct hfsplus_fork_raw rsrc_fork;
|
||||
} __packed;
|
||||
|
||||
/* 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 {
|
||||
__be16 type;
|
||||
s16 reserved;
|
||||
hfsplus_cnid parentID;
|
||||
struct hfsplus_unistr nodeName;
|
||||
} __packed;
|
||||
|
||||
#define HFSPLUS_MIN_THREAD_SZ 10
|
||||
|
||||
/* A data record in the catalog tree */
|
||||
typedef union {
|
||||
__be16 type;
|
||||
struct hfsplus_cat_folder folder;
|
||||
struct hfsplus_cat_file file;
|
||||
struct hfsplus_cat_thread thread;
|
||||
} __packed hfsplus_cat_entry;
|
||||
|
||||
/* HFS+ catalog entry type */
|
||||
#define HFSPLUS_FOLDER 0x0001
|
||||
#define HFSPLUS_FILE 0x0002
|
||||
#define HFSPLUS_FOLDER_THREAD 0x0003
|
||||
#define HFSPLUS_FILE_THREAD 0x0004
|
||||
|
||||
/* HFS+ extents tree key */
|
||||
struct hfsplus_ext_key {
|
||||
__be16 key_len;
|
||||
u8 fork_type;
|
||||
u8 pad;
|
||||
hfsplus_cnid cnid;
|
||||
__be32 start_block;
|
||||
} __packed;
|
||||
|
||||
#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
|
614
fs/hfsplus/inode.c
Normal file
614
fs/hfsplus/inode.c
Normal file
|
@ -0,0 +1,614 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/inode.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
return block_read_full_page(page, hfsplus_get_block);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*pagep = NULL;
|
||||
ret = cont_write_begin(file, mapping, pos, len, flags, pagep, fsdata,
|
||||
hfsplus_get_block,
|
||||
&HFSPLUS_I(mapping->host)->phys_size);
|
||||
if (unlikely(ret))
|
||||
hfsplus_write_failed(mapping, pos + len);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static sector_t hfsplus_bmap(struct address_space *mapping, sector_t block)
|
||||
{
|
||||
return generic_block_bmap(mapping, block, hfsplus_get_block);
|
||||
}
|
||||
|
||||
static int hfsplus_releasepage(struct page *page, gfp_t mask)
|
||||
{
|
||||
struct inode *inode = page->mapping->host;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct hfs_btree *tree;
|
||||
struct hfs_bnode *node;
|
||||
u32 nidx;
|
||||
int i, res = 1;
|
||||
|
||||
switch (inode->i_ino) {
|
||||
case HFSPLUS_EXT_CNID:
|
||||
tree = HFSPLUS_SB(sb)->ext_tree;
|
||||
break;
|
||||
case HFSPLUS_CAT_CNID:
|
||||
tree = HFSPLUS_SB(sb)->cat_tree;
|
||||
break;
|
||||
case HFSPLUS_ATTR_CNID:
|
||||
tree = HFSPLUS_SB(sb)->attr_tree;
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
return 0;
|
||||
}
|
||||
if (!tree)
|
||||
return 0;
|
||||
if (tree->node_size >= PAGE_CACHE_SIZE) {
|
||||
nidx = page->index >>
|
||||
(tree->node_size_shift - PAGE_CACHE_SHIFT);
|
||||
spin_lock(&tree->hash_lock);
|
||||
node = hfs_bnode_findhash(tree, nidx);
|
||||
if (!node)
|
||||
;
|
||||
else if (atomic_read(&node->refcnt))
|
||||
res = 0;
|
||||
if (res && node) {
|
||||
hfs_bnode_unhash(node);
|
||||
hfs_bnode_free(node);
|
||||
}
|
||||
spin_unlock(&tree->hash_lock);
|
||||
} else {
|
||||
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 {
|
||||
node = hfs_bnode_findhash(tree, nidx++);
|
||||
if (!node)
|
||||
continue;
|
||||
if (atomic_read(&node->refcnt)) {
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
hfs_bnode_unhash(node);
|
||||
hfs_bnode_free(node);
|
||||
} while (--i && nidx < tree->node_count);
|
||||
spin_unlock(&tree->hash_lock);
|
||||
}
|
||||
return res ? try_to_free_buffers(page) : 0;
|
||||
}
|
||||
|
||||
static ssize_t hfsplus_direct_IO(int rw, struct kiocb *iocb,
|
||||
struct iov_iter *iter, loff_t offset)
|
||||
{
|
||||
struct file *file = iocb->ki_filp;
|
||||
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, iter, offset,
|
||||
hfsplus_get_block);
|
||||
|
||||
/*
|
||||
* In case of error extending write may have instantiated a few
|
||||
* blocks outside i_size. Trim these off again.
|
||||
*/
|
||||
if (unlikely((rw & WRITE) && ret < 0)) {
|
||||
loff_t isize = i_size_read(inode);
|
||||
loff_t end = offset + count;
|
||||
|
||||
if (end > isize)
|
||||
hfsplus_write_failed(mapping, end);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hfsplus_writepages(struct address_space *mapping,
|
||||
struct writeback_control *wbc)
|
||||
{
|
||||
return mpage_writepages(mapping, wbc, hfsplus_get_block);
|
||||
}
|
||||
|
||||
const struct address_space_operations hfsplus_btree_aops = {
|
||||
.readpage = hfsplus_readpage,
|
||||
.writepage = hfsplus_writepage,
|
||||
.write_begin = hfsplus_write_begin,
|
||||
.write_end = generic_write_end,
|
||||
.bmap = hfsplus_bmap,
|
||||
.releasepage = hfsplus_releasepage,
|
||||
};
|
||||
|
||||
const struct address_space_operations hfsplus_aops = {
|
||||
.readpage = hfsplus_readpage,
|
||||
.writepage = hfsplus_writepage,
|
||||
.write_begin = hfsplus_write_begin,
|
||||
.write_end = generic_write_end,
|
||||
.bmap = hfsplus_bmap,
|
||||
.direct_IO = hfsplus_direct_IO,
|
||||
.writepages = hfsplus_writepages,
|
||||
};
|
||||
|
||||
const struct dentry_operations hfsplus_dentry_operations = {
|
||||
.d_hash = hfsplus_hash_dentry,
|
||||
.d_compare = hfsplus_compare_dentry,
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
i_uid_write(inode, be32_to_cpu(perms->owner));
|
||||
if (!i_uid_read(inode) && !mode)
|
||||
inode->i_uid = sbi->uid;
|
||||
|
||||
i_gid_write(inode, be32_to_cpu(perms->group));
|
||||
if (!i_gid_read(inode) && !mode)
|
||||
inode->i_gid = sbi->gid;
|
||||
|
||||
if (dir) {
|
||||
mode = mode ? (mode & S_IALLUGO) : (S_IRWXUGO & ~(sbi->umask));
|
||||
mode |= S_IFDIR;
|
||||
} else if (!mode)
|
||||
mode = S_IFREG | ((S_IRUGO|S_IWUGO) & ~(sbi->umask));
|
||||
inode->i_mode = mode;
|
||||
|
||||
HFSPLUS_I(inode)->userflags = perms->userflags;
|
||||
if (perms->rootflags & HFSPLUS_FLG_IMMUTABLE)
|
||||
inode->i_flags |= S_IMMUTABLE;
|
||||
else
|
||||
inode->i_flags &= ~S_IMMUTABLE;
|
||||
if (perms->rootflags & HFSPLUS_FLG_APPEND)
|
||||
inode->i_flags |= S_APPEND;
|
||||
else
|
||||
inode->i_flags &= ~S_APPEND;
|
||||
}
|
||||
|
||||
static int hfsplus_file_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
if (HFSPLUS_IS_RSRC(inode))
|
||||
inode = HFSPLUS_I(inode)->rsrc_inode;
|
||||
if (!(file->f_flags & O_LARGEFILE) && i_size_read(inode) > MAX_NON_LFS)
|
||||
return -EOVERFLOW;
|
||||
atomic_inc(&HFSPLUS_I(inode)->opencnt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hfsplus_file_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
|
||||
if (HFSPLUS_IS_RSRC(inode))
|
||||
inode = HFSPLUS_I(inode)->rsrc_inode;
|
||||
if (atomic_dec_and_test(&HFSPLUS_I(inode)->opencnt)) {
|
||||
mutex_lock(&inode->i_mutex);
|
||||
hfsplus_file_truncate(inode);
|
||||
if (inode->i_flags & S_DEAD) {
|
||||
hfsplus_delete_cat(inode->i_ino,
|
||||
HFSPLUS_SB(sb)->hidden_dir, NULL);
|
||||
hfsplus_delete_inode(inode);
|
||||
}
|
||||
mutex_unlock(&inode->i_mutex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hfsplus_setattr(struct dentry *dentry, struct iattr *attr)
|
||||
{
|
||||
struct inode *inode = dentry->d_inode;
|
||||
int error;
|
||||
|
||||
error = inode_change_ok(inode, attr);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
if ((attr->ia_valid & ATTR_SIZE) &&
|
||||
attr->ia_size != i_size_read(inode)) {
|
||||
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;
|
||||
}
|
||||
|
||||
int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,
|
||||
int datasync)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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 = {
|
||||
.setattr = hfsplus_setattr,
|
||||
.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 = 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,
|
||||
.open = hfsplus_file_open,
|
||||
.release = hfsplus_file_release,
|
||||
.unlocked_ioctl = hfsplus_ioctl,
|
||||
};
|
||||
|
||||
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);
|
||||
struct hfsplus_inode_info *hip;
|
||||
|
||||
if (!inode)
|
||||
return NULL;
|
||||
|
||||
inode->i_ino = sbi->next_cnid++;
|
||||
inode->i_mode = mode;
|
||||
inode->i_uid = current_fsuid();
|
||||
inode->i_gid = current_fsgid();
|
||||
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;
|
||||
hip->first_blocks = 0;
|
||||
hip->cached_start = 0;
|
||||
hip->cached_blocks = 0;
|
||||
hip->phys_size = 0;
|
||||
hip->fs_blocks = 0;
|
||||
hip->rsrc_inode = NULL;
|
||||
if (S_ISDIR(inode->i_mode)) {
|
||||
inode->i_size = 2;
|
||||
sbi->folder_count++;
|
||||
inode->i_op = &hfsplus_dir_inode_operations;
|
||||
inode->i_fop = &hfsplus_dir_operations;
|
||||
} else if (S_ISREG(inode->i_mode)) {
|
||||
sbi->file_count++;
|
||||
inode->i_op = &hfsplus_file_inode_operations;
|
||||
inode->i_fop = &hfsplus_file_operations;
|
||||
inode->i_mapping->a_ops = &hfsplus_aops;
|
||||
hip->clump_blocks = sbi->data_clump_blocks;
|
||||
} else if (S_ISLNK(inode->i_mode)) {
|
||||
sbi->file_count++;
|
||||
inode->i_op = &page_symlink_inode_operations;
|
||||
inode->i_mapping->a_ops = &hfsplus_aops;
|
||||
hip->clump_blocks = 1;
|
||||
} else
|
||||
sbi->file_count++;
|
||||
insert_inode_hash(inode);
|
||||
mark_inode_dirty(inode);
|
||||
hfsplus_mark_mdb_dirty(sb);
|
||||
|
||||
return inode;
|
||||
}
|
||||
|
||||
void hfsplus_delete_inode(struct inode *inode)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
|
||||
if (S_ISDIR(inode->i_mode)) {
|
||||
HFSPLUS_SB(sb)->folder_count--;
|
||||
hfsplus_mark_mdb_dirty(sb);
|
||||
return;
|
||||
}
|
||||
HFSPLUS_SB(sb)->file_count--;
|
||||
if (S_ISREG(inode->i_mode)) {
|
||||
if (!inode->i_nlink) {
|
||||
inode->i_size = 0;
|
||||
hfsplus_file_truncate(inode);
|
||||
}
|
||||
} else if (S_ISLNK(inode->i_mode)) {
|
||||
inode->i_size = 0;
|
||||
hfsplus_file_truncate(inode);
|
||||
}
|
||||
hfsplus_mark_mdb_dirty(sb);
|
||||
}
|
||||
|
||||
void hfsplus_inode_read_fork(struct inode *inode, struct hfsplus_fork_raw *fork)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
|
||||
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
|
||||
u32 count;
|
||||
int i;
|
||||
|
||||
memcpy(&hip->first_extents, &fork->extents, sizeof(hfsplus_extent_rec));
|
||||
for (count = 0, i = 0; i < 8; i++)
|
||||
count += be32_to_cpu(fork->extents[i].block_count);
|
||||
hip->first_blocks = count;
|
||||
memset(hip->cached_extents, 0, sizeof(hfsplus_extent_rec));
|
||||
hip->cached_start = 0;
|
||||
hip->cached_blocks = 0;
|
||||
|
||||
hip->alloc_blocks = be32_to_cpu(fork->total_blocks);
|
||||
hip->phys_size = inode->i_size = be64_to_cpu(fork->total_size);
|
||||
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);
|
||||
hip->clump_blocks =
|
||||
be32_to_cpu(fork->clump_size) >> sbi->alloc_blksz_shift;
|
||||
if (!hip->clump_blocks) {
|
||||
hip->clump_blocks = HFSPLUS_IS_RSRC(inode) ?
|
||||
sbi->rsrc_clump_blocks :
|
||||
sbi->data_clump_blocks;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
fork->total_size = cpu_to_be64(inode->i_size);
|
||||
fork->total_blocks = cpu_to_be32(HFSPLUS_I(inode)->alloc_blocks);
|
||||
}
|
||||
|
||||
int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd)
|
||||
{
|
||||
hfsplus_cat_entry entry;
|
||||
int res = 0;
|
||||
u16 type;
|
||||
|
||||
type = hfs_bnode_read_u16(fd->bnode, fd->entryoffset);
|
||||
|
||||
HFSPLUS_I(inode)->linkid = 0;
|
||||
if (type == HFSPLUS_FOLDER) {
|
||||
struct hfsplus_cat_folder *folder = &entry.folder;
|
||||
|
||||
if (fd->entrylength < sizeof(struct hfsplus_cat_folder))
|
||||
/* panic? */;
|
||||
hfs_bnode_read(fd->bnode, &entry, fd->entryoffset,
|
||||
sizeof(struct hfsplus_cat_folder));
|
||||
hfsplus_get_perms(inode, &folder->permissions, 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) {
|
||||
struct hfsplus_cat_file *file = &entry.file;
|
||||
|
||||
if (fd->entrylength < sizeof(struct hfsplus_cat_file))
|
||||
/* panic? */;
|
||||
hfs_bnode_read(fd->bnode, &entry, fd->entryoffset,
|
||||
sizeof(struct hfsplus_cat_file));
|
||||
|
||||
hfsplus_inode_read_fork(inode, HFSPLUS_IS_RSRC(inode) ?
|
||||
&file->rsrc_fork : &file->data_fork);
|
||||
hfsplus_get_perms(inode, &file->permissions, 0);
|
||||
set_nlink(inode, 1);
|
||||
if (S_ISREG(inode->i_mode)) {
|
||||
if (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;
|
||||
} else if (S_ISLNK(inode->i_mode)) {
|
||||
inode->i_op = &page_symlink_inode_operations;
|
||||
inode->i_mapping->a_ops = &hfsplus_aops;
|
||||
} else {
|
||||
init_special_inode(inode, inode->i_mode,
|
||||
be32_to_cpu(file->permissions.dev));
|
||||
}
|
||||
inode->i_atime = hfsp_mt2ut(file->access_date);
|
||||
inode->i_mtime = hfsp_mt2ut(file->content_mod_date);
|
||||
inode->i_ctime = hfsp_mt2ut(file->attribute_mod_date);
|
||||
HFSPLUS_I(inode)->create_date = file->create_date;
|
||||
} else {
|
||||
pr_err("bad catalog entry used to create inode\n");
|
||||
res = -EIO;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int hfsplus_cat_write_inode(struct inode *inode)
|
||||
{
|
||||
struct inode *main_inode = inode;
|
||||
struct hfs_find_data fd;
|
||||
hfsplus_cat_entry entry;
|
||||
|
||||
if (HFSPLUS_IS_RSRC(inode))
|
||||
main_inode = HFSPLUS_I(inode)->rsrc_inode;
|
||||
|
||||
if (!main_inode->i_nlink)
|
||||
return 0;
|
||||
|
||||
if (hfs_find_init(HFSPLUS_SB(main_inode->i_sb)->cat_tree, &fd))
|
||||
/* panic? */
|
||||
return -EIO;
|
||||
|
||||
if (hfsplus_find_cat(main_inode->i_sb, main_inode->i_ino, &fd))
|
||||
/* panic? */
|
||||
goto out;
|
||||
|
||||
if (S_ISDIR(main_inode->i_mode)) {
|
||||
struct hfsplus_cat_folder *folder = &entry.folder;
|
||||
|
||||
if (fd.entrylength < sizeof(struct hfsplus_cat_folder))
|
||||
/* panic? */;
|
||||
hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
|
||||
sizeof(struct hfsplus_cat_folder));
|
||||
/* simple node checks? */
|
||||
hfsplus_cat_set_perms(inode, &folder->permissions);
|
||||
folder->access_date = hfsp_ut2mt(inode->i_atime);
|
||||
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)) {
|
||||
struct hfsplus_cat_file *file = &entry.file;
|
||||
hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
|
||||
sizeof(struct hfsplus_cat_file));
|
||||
hfsplus_inode_write_fork(inode, &file->rsrc_fork);
|
||||
hfs_bnode_write(fd.bnode, &entry, fd.entryoffset,
|
||||
sizeof(struct hfsplus_cat_file));
|
||||
} else {
|
||||
struct hfsplus_cat_file *file = &entry.file;
|
||||
|
||||
if (fd.entrylength < sizeof(struct hfsplus_cat_file))
|
||||
/* panic? */;
|
||||
hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
|
||||
sizeof(struct hfsplus_cat_file));
|
||||
hfsplus_inode_write_fork(inode, &file->data_fork);
|
||||
hfsplus_cat_set_perms(inode, &file->permissions);
|
||||
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);
|
||||
file->access_date = hfsp_ut2mt(inode->i_atime);
|
||||
file->content_mod_date = hfsp_ut2mt(inode->i_mtime);
|
||||
file->attribute_mod_date = hfsp_ut2mt(inode->i_ctime);
|
||||
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;
|
||||
}
|
152
fs/hfsplus/ioctl.c
Normal file
152
fs/hfsplus/ioctl.c
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/ioctl.c
|
||||
*
|
||||
* Copyright (C) 2003
|
||||
* Ethan Benson <erbenson@alaska.net>
|
||||
* partially derived from linux/fs/ext2/ioctl.c
|
||||
* Copyright (C) 1993, 1994, 1995
|
||||
* Remy Card (card@masi.ibp.fr)
|
||||
* Laboratoire MASI - Institut Blaise Pascal
|
||||
* Universite Pierre et Marie Curie (Paris VI)
|
||||
*
|
||||
* hfsplus ioctls
|
||||
*/
|
||||
|
||||
#include <linux/capability.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/sched.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_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)
|
||||
flags |= FS_APPEND_FL;
|
||||
if (hip->userflags & HFSPLUS_FLG_NODUMP)
|
||||
flags |= FS_NODUMP_FL;
|
||||
|
||||
return put_user(flags, user_flags);
|
||||
}
|
||||
|
||||
static int hfsplus_ioctl_setflags(struct file *file, int __user *user_flags)
|
||||
{
|
||||
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(file);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
if (!inode_owner_or_capable(inode)) {
|
||||
err = -EACCES;
|
||||
goto out_drop_write;
|
||||
}
|
||||
|
||||
if (get_user(flags, user_flags)) {
|
||||
err = -EFAULT;
|
||||
goto out_drop_write;
|
||||
}
|
||||
|
||||
mutex_lock(&inode->i_mutex);
|
||||
|
||||
if ((flags & (FS_IMMUTABLE_FL|FS_APPEND_FL)) ||
|
||||
inode->i_flags & (S_IMMUTABLE|S_APPEND)) {
|
||||
if (!capable(CAP_LINUX_IMMUTABLE)) {
|
||||
err = -EPERM;
|
||||
goto out_unlock_inode;
|
||||
}
|
||||
}
|
||||
|
||||
/* don't silently ignore unsupported ext2 flags */
|
||||
if (flags & ~(FS_IMMUTABLE_FL|FS_APPEND_FL|FS_NODUMP_FL)) {
|
||||
err = -EOPNOTSUPP;
|
||||
goto out_unlock_inode;
|
||||
}
|
||||
|
||||
if (flags & FS_IMMUTABLE_FL)
|
||||
inode->i_flags |= S_IMMUTABLE;
|
||||
else
|
||||
inode->i_flags &= ~S_IMMUTABLE;
|
||||
|
||||
if (flags & FS_APPEND_FL)
|
||||
inode->i_flags |= S_APPEND;
|
||||
else
|
||||
inode->i_flags &= ~S_APPEND;
|
||||
|
||||
if (flags & FS_NODUMP_FL)
|
||||
hip->userflags |= HFSPLUS_FLG_NODUMP;
|
||||
else
|
||||
hip->userflags &= ~HFSPLUS_FLG_NODUMP;
|
||||
|
||||
inode->i_ctime = CURRENT_TIME_SEC;
|
||||
mark_inode_dirty(inode);
|
||||
|
||||
out_unlock_inode:
|
||||
mutex_unlock(&inode->i_mutex);
|
||||
out_drop_write:
|
||||
mnt_drop_write_file(file);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
long hfsplus_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
void __user *argp = (void __user *)arg;
|
||||
|
||||
switch (cmd) {
|
||||
case HFSPLUS_IOC_EXT2_GETFLAGS:
|
||||
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;
|
||||
}
|
||||
}
|
238
fs/hfsplus/options.c
Normal file
238
fs/hfsplus/options.c
Normal file
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/options.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* Option parsing
|
||||
*/
|
||||
|
||||
#include <linux/string.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/parser.h>
|
||||
#include <linux/nls.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include "hfsplus_fs.h"
|
||||
|
||||
enum {
|
||||
opt_creator, opt_type,
|
||||
opt_umask, opt_uid, opt_gid,
|
||||
opt_part, opt_session, opt_nls,
|
||||
opt_nodecompose, opt_decompose,
|
||||
opt_barrier, opt_nobarrier,
|
||||
opt_force, opt_err
|
||||
};
|
||||
|
||||
static const match_table_t tokens = {
|
||||
{ opt_creator, "creator=%s" },
|
||||
{ opt_type, "type=%s" },
|
||||
{ opt_umask, "umask=%o" },
|
||||
{ opt_uid, "uid=%u" },
|
||||
{ opt_gid, "gid=%u" },
|
||||
{ opt_part, "part=%u" },
|
||||
{ opt_session, "session=%u" },
|
||||
{ opt_nls, "nls=%s" },
|
||||
{ opt_decompose, "decompose" },
|
||||
{ opt_nodecompose, "nodecompose" },
|
||||
{ opt_barrier, "barrier" },
|
||||
{ opt_nobarrier, "nobarrier" },
|
||||
{ opt_force, "force" },
|
||||
{ opt_err, NULL }
|
||||
};
|
||||
|
||||
/* Initialize an options object to reasonable defaults */
|
||||
void hfsplus_fill_defaults(struct hfsplus_sb_info *opts)
|
||||
{
|
||||
if (!opts)
|
||||
return;
|
||||
|
||||
opts->creator = HFSPLUS_DEF_CR_TYPE;
|
||||
opts->type = HFSPLUS_DEF_CR_TYPE;
|
||||
opts->umask = current_umask();
|
||||
opts->uid = current_uid();
|
||||
opts->gid = current_gid();
|
||||
opts->part = -1;
|
||||
opts->session = -1;
|
||||
}
|
||||
|
||||
/* convert a "four byte character" to a 32 bit int with error checks */
|
||||
static inline int match_fourchar(substring_t *arg, u32 *result)
|
||||
{
|
||||
if (arg->to - arg->from != 4)
|
||||
return -EINVAL;
|
||||
memcpy(result, arg->from, 4);
|
||||
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)
|
||||
{
|
||||
char *p;
|
||||
substring_t args[MAX_OPT_ARGS];
|
||||
int tmp, token;
|
||||
|
||||
if (!input)
|
||||
goto done;
|
||||
|
||||
while ((p = strsep(&input, ",")) != NULL) {
|
||||
if (!*p)
|
||||
continue;
|
||||
|
||||
token = match_token(p, tokens, args);
|
||||
switch (token) {
|
||||
case opt_creator:
|
||||
if (match_fourchar(&args[0], &sbi->creator)) {
|
||||
pr_err("creator requires a 4 character value\n");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case opt_type:
|
||||
if (match_fourchar(&args[0], &sbi->type)) {
|
||||
pr_err("type requires a 4 character value\n");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case opt_umask:
|
||||
if (match_octal(&args[0], &tmp)) {
|
||||
pr_err("umask requires a value\n");
|
||||
return 0;
|
||||
}
|
||||
sbi->umask = (umode_t)tmp;
|
||||
break;
|
||||
case opt_uid:
|
||||
if (match_int(&args[0], &tmp)) {
|
||||
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;
|
||||
}
|
||||
break;
|
||||
case opt_gid:
|
||||
if (match_int(&args[0], &tmp)) {
|
||||
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;
|
||||
}
|
||||
break;
|
||||
case opt_part:
|
||||
if (match_int(&args[0], &sbi->part)) {
|
||||
pr_err("part requires an argument\n");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case opt_session:
|
||||
if (match_int(&args[0], &sbi->session)) {
|
||||
pr_err("session requires an argument\n");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case opt_nls:
|
||||
if (sbi->nls) {
|
||||
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) {
|
||||
pr_err("unable to load nls mapping \"%s\"\n",
|
||||
p);
|
||||
kfree(p);
|
||||
return 0;
|
||||
}
|
||||
kfree(p);
|
||||
break;
|
||||
case opt_decompose:
|
||||
clear_bit(HFSPLUS_SB_NODECOMPOSE, &sbi->flags);
|
||||
break;
|
||||
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;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (!sbi->nls) {
|
||||
/* try utf8 first, as this is the old default behaviour */
|
||||
sbi->nls = load_nls("utf8");
|
||||
if (!sbi->nls)
|
||||
sbi->nls = load_nls_default();
|
||||
if (!sbi->nls)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int hfsplus_show_options(struct seq_file *seq, struct dentry *root)
|
||||
{
|
||||
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,
|
||||
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)
|
||||
seq_printf(seq, ",session=%u", sbi->session);
|
||||
if (sbi->nls)
|
||||
seq_printf(seq, ",nls=%s", sbi->nls->charset);
|
||||
if (test_bit(HFSPLUS_SB_NODECOMPOSE, &sbi->flags))
|
||||
seq_puts(seq, ",nodecompose");
|
||||
if (test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags))
|
||||
seq_puts(seq, ",nobarrier");
|
||||
return 0;
|
||||
}
|
157
fs/hfsplus/part_tbl.c
Normal file
157
fs/hfsplus/part_tbl.c
Normal file
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
* Original code to handle the new style Mac partition table based on
|
||||
* a patch contributed by Holger Schemel (aeglos@valinor.owl.de).
|
||||
*
|
||||
* In function preconditions the term "valid" applied to a pointer to
|
||||
* a structure means that the pointer is non-NULL and the structure it
|
||||
* points to has all fields initialized to consistent values.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include "hfsplus_fs.h"
|
||||
|
||||
/* offsets to various blocks */
|
||||
#define HFS_DD_BLK 0 /* Driver Descriptor block */
|
||||
#define HFS_PMAP_BLK 1 /* First block of partition map */
|
||||
#define HFS_MDB_BLK 2 /* Block (w/i partition) of MDB */
|
||||
|
||||
/* magic numbers for various disk blocks */
|
||||
#define HFS_DRVR_DESC_MAGIC 0x4552 /* "ER": driver descriptor map */
|
||||
#define HFS_OLD_PMAP_MAGIC 0x5453 /* "TS": old-type partition map */
|
||||
#define HFS_NEW_PMAP_MAGIC 0x504D /* "PM": new-type partition map */
|
||||
#define HFS_SUPER_MAGIC 0x4244 /* "BD": HFS MDB (super block) */
|
||||
#define HFS_MFS_SUPER_MAGIC 0xD2D7 /* MFS MDB (super block) */
|
||||
|
||||
/*
|
||||
* The new style Mac partition map
|
||||
*
|
||||
* For each partition on the media there is a physical block (512-byte
|
||||
* block) containing one of these structures. These blocks are
|
||||
* contiguous starting at block 1.
|
||||
*/
|
||||
struct new_pmap {
|
||||
__be16 pmSig; /* signature */
|
||||
__be16 reSigPad; /* padding */
|
||||
__be32 pmMapBlkCnt; /* partition blocks count */
|
||||
__be32 pmPyPartStart; /* physical block start of partition */
|
||||
__be32 pmPartBlkCnt; /* physical block count of partition */
|
||||
u8 pmPartName[32]; /* (null terminated?) string
|
||||
giving the name of this
|
||||
partition */
|
||||
u8 pmPartType[32]; /* (null terminated?) string
|
||||
giving the type of this
|
||||
partition */
|
||||
/* a bunch more stuff we don't need */
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* The old style Mac partition map
|
||||
*
|
||||
* The partition map consists for a 2-byte signature followed by an
|
||||
* array of these structures. The map is terminated with an all-zero
|
||||
* one of these.
|
||||
*/
|
||||
struct old_pmap {
|
||||
__be16 pdSig; /* Signature bytes */
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
void *buf, *data;
|
||||
int res;
|
||||
|
||||
buf = kmalloc(hfsplus_min_io_size(sb), GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
res = hfsplus_submit_bio(sb, *part_start + HFS_PMAP_BLK,
|
||||
buf, &data, READ);
|
||||
if (res)
|
||||
goto out;
|
||||
|
||||
switch (be16_to_cpu(*((__be16 *)data))) {
|
||||
case HFS_OLD_PMAP_MAGIC:
|
||||
res = hfs_parse_old_pmap(sb, data, part_start, part_size);
|
||||
break;
|
||||
case HFS_NEW_PMAP_MAGIC:
|
||||
res = hfs_parse_new_pmap(sb, buf, data, part_start, part_size);
|
||||
break;
|
||||
default:
|
||||
res = -ENOENT;
|
||||
break;
|
||||
}
|
||||
out:
|
||||
kfree(buf);
|
||||
return res;
|
||||
}
|
140
fs/hfsplus/posix_acl.c
Normal file
140
fs/hfsplus/posix_acl.c
Normal file
|
@ -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;
|
||||
}
|
698
fs/hfsplus/super.c
Normal file
698
fs/hfsplus/super.c
Normal file
|
@ -0,0 +1,698 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/super.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#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>
|
||||
#include <linux/nls.h>
|
||||
|
||||
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)
|
||||
{
|
||||
struct hfsplus_vh *vhdr = HFSPLUS_SB(inode->i_sb)->s_vhdr;
|
||||
|
||||
switch (inode->i_ino) {
|
||||
case HFSPLUS_EXT_CNID:
|
||||
hfsplus_inode_read_fork(inode, &vhdr->ext_file);
|
||||
inode->i_mapping->a_ops = &hfsplus_btree_aops;
|
||||
break;
|
||||
case HFSPLUS_CAT_CNID:
|
||||
hfsplus_inode_read_fork(inode, &vhdr->cat_file);
|
||||
inode->i_mapping->a_ops = &hfsplus_btree_aops;
|
||||
break;
|
||||
case HFSPLUS_ALLOC_CNID:
|
||||
hfsplus_inode_read_fork(inode, &vhdr->alloc_file);
|
||||
inode->i_mapping->a_ops = &hfsplus_aops;
|
||||
break;
|
||||
case HFSPLUS_START_CNID:
|
||||
hfsplus_inode_read_fork(inode, &vhdr->start_file);
|
||||
break;
|
||||
case HFSPLUS_ATTR_CNID:
|
||||
hfsplus_inode_read_fork(inode, &vhdr->attr_file);
|
||||
inode->i_mapping->a_ops = &hfsplus_btree_aops;
|
||||
break;
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct inode *hfsplus_iget(struct super_block *sb, unsigned long ino)
|
||||
{
|
||||
struct hfs_find_data fd;
|
||||
struct inode *inode;
|
||||
int err;
|
||||
|
||||
inode = iget_locked(sb, ino);
|
||||
if (!inode)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
if (!(inode->i_state & I_NEW))
|
||||
return inode;
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
iget_failed(inode);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
unlock_new_inode(inode);
|
||||
return inode;
|
||||
}
|
||||
|
||||
static int hfsplus_system_write_inode(struct inode *inode)
|
||||
{
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
|
||||
struct hfsplus_vh *vhdr = sbi->s_vhdr;
|
||||
struct hfsplus_fork_raw *fork;
|
||||
struct hfs_btree *tree = NULL;
|
||||
|
||||
switch (inode->i_ino) {
|
||||
case HFSPLUS_EXT_CNID:
|
||||
fork = &vhdr->ext_file;
|
||||
tree = sbi->ext_tree;
|
||||
break;
|
||||
case HFSPLUS_CAT_CNID:
|
||||
fork = &vhdr->cat_file;
|
||||
tree = sbi->cat_tree;
|
||||
break;
|
||||
case HFSPLUS_ALLOC_CNID:
|
||||
fork = &vhdr->alloc_file;
|
||||
break;
|
||||
case HFSPLUS_START_CNID:
|
||||
fork = &vhdr->start_file;
|
||||
break;
|
||||
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);
|
||||
hfsplus_mark_mdb_dirty(inode->i_sb);
|
||||
}
|
||||
hfsplus_inode_write_fork(inode, fork);
|
||||
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)
|
||||
{
|
||||
int err;
|
||||
|
||||
hfs_dbg(INODE, "hfsplus_write_inode: %lu\n", inode->i_ino);
|
||||
|
||||
err = hfsplus_ext_write_extent(inode);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (inode->i_ino >= HFSPLUS_FIRSTUSER_CNID ||
|
||||
inode->i_ino == HFSPLUS_ROOT_CNID)
|
||||
return hfsplus_cat_write_inode(inode);
|
||||
else
|
||||
return hfsplus_system_write_inode(inode);
|
||||
}
|
||||
|
||||
static void hfsplus_evict_inode(struct inode *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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
if (test_and_clear_bit(HFSPLUS_SB_WRITEBACKUP, &sbi->flags)) {
|
||||
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);
|
||||
|
||||
if (!test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags))
|
||||
blkdev_issue_flush(sb->s_bdev, GFP_KERNEL, NULL);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void delayed_sync_fs(struct work_struct *work)
|
||||
{
|
||||
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);
|
||||
|
||||
hfs_dbg(SUPER, "hfsplus_put_super\n");
|
||||
|
||||
cancel_delayed_work_sync(&sbi->sync_work);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
static int hfsplus_statfs(struct dentry *dentry, struct kstatfs *buf)
|
||||
{
|
||||
struct super_block *sb = dentry->d_sb;
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
|
||||
u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
|
||||
|
||||
buf->f_type = HFSPLUS_SUPER_MAGIC;
|
||||
buf->f_bsize = sb->s_blocksize;
|
||||
buf->f_blocks = sbi->total_blocks << sbi->fs_shift;
|
||||
buf->f_bfree = sbi->free_blocks << sbi->fs_shift;
|
||||
buf->f_bavail = buf->f_bfree;
|
||||
buf->f_files = 0xFFFFFFFF;
|
||||
buf->f_ffree = 0xFFFFFFFF - sbi->next_cnid;
|
||||
buf->f_fsid.val[0] = (u32)id;
|
||||
buf->f_fsid.val[1] = (u32)(id >> 32);
|
||||
buf->f_namelen = HFSPLUS_MAX_STRLEN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
int force = 0;
|
||||
|
||||
if (!hfsplus_parse_options_remount(data, &force))
|
||||
return -EINVAL;
|
||||
|
||||
if (!(vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_UNMNT))) {
|
||||
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 (force) {
|
||||
/* nothing */
|
||||
} 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)) {
|
||||
pr_warn("filesystem is marked journaled, leaving read-only.\n");
|
||||
sb->s_flags |= MS_RDONLY;
|
||||
*flags |= MS_RDONLY;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct super_operations hfsplus_sops = {
|
||||
.alloc_inode = hfsplus_alloc_inode,
|
||||
.destroy_inode = hfsplus_destroy_inode,
|
||||
.write_inode = hfsplus_write_inode,
|
||||
.evict_inode = hfsplus_evict_inode,
|
||||
.put_super = hfsplus_put_super,
|
||||
.sync_fs = hfsplus_sync_fs,
|
||||
.statfs = hfsplus_statfs,
|
||||
.remount_fs = hfsplus_remount,
|
||||
.show_options = hfsplus_show_options,
|
||||
};
|
||||
|
||||
static int hfsplus_fill_super(struct super_block *sb, void *data, int silent)
|
||||
{
|
||||
struct hfsplus_vh *vhdr;
|
||||
struct hfsplus_sb_info *sbi;
|
||||
hfsplus_cat_entry entry;
|
||||
struct hfs_find_data fd;
|
||||
struct inode *root, *inode;
|
||||
struct qstr str;
|
||||
struct nls_table *nls = NULL;
|
||||
u64 last_fs_block, last_fs_page;
|
||||
int err;
|
||||
|
||||
err = -ENOMEM;
|
||||
sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
|
||||
if (!sbi)
|
||||
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)) {
|
||||
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) {
|
||||
pr_err("unable to load nls for utf8\n");
|
||||
goto out_unload_nls;
|
||||
}
|
||||
|
||||
/* Grab the volume header */
|
||||
if (hfsplus_read_wrapper(sb)) {
|
||||
if (!silent)
|
||||
pr_warn("unable to find HFS+ superblock\n");
|
||||
goto out_unload_nls;
|
||||
}
|
||||
vhdr = sbi->s_vhdr;
|
||||
|
||||
/* Copy parts of the volume header into the superblock */
|
||||
sb->s_magic = HFSPLUS_VOLHEAD_SIG;
|
||||
if (be16_to_cpu(vhdr->version) < HFSPLUS_MIN_VERSION ||
|
||||
be16_to_cpu(vhdr->version) > HFSPLUS_CURRENT_VERSION) {
|
||||
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);
|
||||
sbi->next_cnid = be32_to_cpu(vhdr->next_cnid);
|
||||
sbi->file_count = be32_to_cpu(vhdr->file_count);
|
||||
sbi->folder_count = be32_to_cpu(vhdr->folder_count);
|
||||
sbi->data_clump_blocks =
|
||||
be32_to_cpu(vhdr->data_clump_sz) >> sbi->alloc_blksz_shift;
|
||||
if (!sbi->data_clump_blocks)
|
||||
sbi->data_clump_blocks = 1;
|
||||
sbi->rsrc_clump_blocks =
|
||||
be32_to_cpu(vhdr->rsrc_clump_sz) >> sbi->alloc_blksz_shift;
|
||||
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))) {
|
||||
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)) {
|
||||
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)) {
|
||||
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) {
|
||||
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) {
|
||||
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)) {
|
||||
pr_err("failed to load allocation file\n");
|
||||
err = PTR_ERR(inode);
|
||||
goto out_close_attr_tree;
|
||||
}
|
||||
sbi->alloc_file = inode;
|
||||
|
||||
/* Load the root directory */
|
||||
root = hfsplus_iget(sb, HFSPLUS_ROOT_CNID);
|
||||
if (IS_ERR(root)) {
|
||||
pr_err("failed to load root directory\n");
|
||||
err = PTR_ERR(root);
|
||||
goto out_put_alloc_file;
|
||||
}
|
||||
|
||||
sb->s_d_op = &hfsplus_dentry_operations;
|
||||
sb->s_root = d_make_root(root);
|
||||
if (!sb->s_root) {
|
||||
err = -ENOMEM;
|
||||
goto out_put_alloc_file;
|
||||
}
|
||||
|
||||
str.len = sizeof(HFSP_HIDDENDIR_NAME) - 1;
|
||||
str.name = HFSP_HIDDENDIR_NAME;
|
||||
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 out_put_root;
|
||||
inode = hfsplus_iget(sb, be32_to_cpu(entry.folder.id));
|
||||
if (IS_ERR(inode)) {
|
||||
err = PTR_ERR(inode);
|
||||
goto out_put_root;
|
||||
}
|
||||
sbi->hidden_dir = inode;
|
||||
} else
|
||||
hfs_find_exit(&fd);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
unload_nls(sbi->nls);
|
||||
sbi->nls = nls;
|
||||
return 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Brad Boyer");
|
||||
MODULE_DESCRIPTION("Extended Macintosh Filesystem");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static struct kmem_cache *hfsplus_inode_cachep;
|
||||
|
||||
static struct inode *hfsplus_alloc_inode(struct super_block *sb)
|
||||
{
|
||||
struct hfsplus_inode_info *i;
|
||||
|
||||
i = kmem_cache_alloc(hfsplus_inode_cachep, GFP_KERNEL);
|
||||
return i ? &i->vfs_inode : NULL;
|
||||
}
|
||||
|
||||
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,
|
||||
int flags, const char *dev_name, void *data)
|
||||
{
|
||||
return mount_bdev(fs_type, flags, dev_name, data, hfsplus_fill_super);
|
||||
}
|
||||
|
||||
static struct file_system_type hfsplus_fs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "hfsplus",
|
||||
.mount = hfsplus_mount,
|
||||
.kill_sb = kill_block_super,
|
||||
.fs_flags = FS_REQUIRES_DEV,
|
||||
};
|
||||
MODULE_ALIAS_FS("hfsplus");
|
||||
|
||||
static void hfsplus_init_once(void *p)
|
||||
{
|
||||
struct hfsplus_inode_info *i = p;
|
||||
|
||||
inode_init_once(&i->vfs_inode);
|
||||
}
|
||||
|
||||
static int __init init_hfsplus_fs(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
hfsplus_inode_cachep = kmem_cache_create("hfsplus_icache",
|
||||
HFSPLUS_INODE_SIZE, 0, SLAB_HWCACHE_ALIGN,
|
||||
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)
|
||||
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);
|
||||
}
|
||||
|
||||
module_init(init_hfsplus_fs)
|
||||
module_exit(exit_hfsplus_fs)
|
3245
fs/hfsplus/tables.c
Normal file
3245
fs/hfsplus/tables.c
Normal file
File diff suppressed because it is too large
Load diff
469
fs/hfsplus/unicode.c
Normal file
469
fs/hfsplus/unicode.c
Normal file
|
@ -0,0 +1,469 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/unicode.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* Handler routines for unicode strings
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/nls.h>
|
||||
#include "hfsplus_fs.h"
|
||||
#include "hfsplus_raw.h"
|
||||
|
||||
/* Fold the case of a unicode char, given the 16 bit value */
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* Compare unicode strings, return values like normal strcmp */
|
||||
int hfsplus_strcasecmp(const struct hfsplus_unistr *s1,
|
||||
const struct hfsplus_unistr *s2)
|
||||
{
|
||||
u16 len1, len2, c1, c2;
|
||||
const hfsplus_unichr *p1, *p2;
|
||||
|
||||
len1 = be16_to_cpu(s1->length);
|
||||
len2 = be16_to_cpu(s2->length);
|
||||
p1 = s1->unicode;
|
||||
p2 = s2->unicode;
|
||||
|
||||
while (1) {
|
||||
c1 = c2 = 0;
|
||||
|
||||
while (len1 && !c1) {
|
||||
c1 = case_fold(be16_to_cpu(*p1));
|
||||
p1++;
|
||||
len1--;
|
||||
}
|
||||
while (len2 && !c2) {
|
||||
c2 = case_fold(be16_to_cpu(*p2));
|
||||
p2++;
|
||||
len2--;
|
||||
}
|
||||
|
||||
if (c1 != c2)
|
||||
return (c1 < c2) ? -1 : 1;
|
||||
if (!c1 && !c2)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Compare names as a sequence of 16-bit unsigned integers */
|
||||
int hfsplus_strcmp(const struct hfsplus_unistr *s1,
|
||||
const struct hfsplus_unistr *s2)
|
||||
{
|
||||
u16 len1, len2, c1, c2;
|
||||
const hfsplus_unichr *p1, *p2;
|
||||
int len;
|
||||
|
||||
len1 = be16_to_cpu(s1->length);
|
||||
len2 = be16_to_cpu(s2->length);
|
||||
p1 = s1->unicode;
|
||||
p2 = s2->unicode;
|
||||
|
||||
for (len = min(len1, len2); len > 0; len--) {
|
||||
c1 = be16_to_cpu(*p1);
|
||||
c2 = be16_to_cpu(*p2);
|
||||
if (c1 != c2)
|
||||
return c1 < c2 ? -1 : 1;
|
||||
p1++;
|
||||
p2++;
|
||||
}
|
||||
|
||||
return len1 < len2 ? -1 :
|
||||
len1 > len2 ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
#define Hangul_SBase 0xac00
|
||||
#define Hangul_LBase 0x1100
|
||||
#define Hangul_VBase 0x1161
|
||||
#define Hangul_TBase 0x11a7
|
||||
#define Hangul_SCount 11172
|
||||
#define Hangul_LCount 19
|
||||
#define Hangul_VCount 21
|
||||
#define Hangul_TCount 28
|
||||
#define Hangul_NCount (Hangul_VCount * Hangul_TCount)
|
||||
|
||||
|
||||
static u16 *hfsplus_compose_lookup(u16 *p, u16 cc)
|
||||
{
|
||||
int i, s, e;
|
||||
|
||||
s = 1;
|
||||
e = p[1];
|
||||
if (!e || cc < p[s * 2] || cc > p[e * 2])
|
||||
return NULL;
|
||||
do {
|
||||
i = (s + e) / 2;
|
||||
if (cc > p[i * 2])
|
||||
s = i + 1;
|
||||
else if (cc < p[i * 2])
|
||||
e = i - 1;
|
||||
else
|
||||
return hfsplus_compose_table + p[i * 2 + 1];
|
||||
} while (s <= e);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
u8 *op;
|
||||
u16 cc, c0, c1;
|
||||
u16 *ce1, *ce2;
|
||||
int i, len, ustrlen, res, compose;
|
||||
|
||||
op = astr;
|
||||
ip = ustr->unicode;
|
||||
ustrlen = be16_to_cpu(ustr->length);
|
||||
len = *len_p;
|
||||
ce1 = NULL;
|
||||
compose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
||||
|
||||
while (ustrlen > 0) {
|
||||
c0 = be16_to_cpu(*ip++);
|
||||
ustrlen--;
|
||||
/* search for single decomposed char */
|
||||
if (likely(compose))
|
||||
ce1 = hfsplus_compose_lookup(hfsplus_compose_table, c0);
|
||||
if (ce1)
|
||||
cc = ce1[0];
|
||||
else
|
||||
cc = 0;
|
||||
if (cc) {
|
||||
/* start of a possibly decomposed Hangul char */
|
||||
if (cc != 0xffff)
|
||||
goto done;
|
||||
if (!ustrlen)
|
||||
goto same;
|
||||
c1 = be16_to_cpu(*ip) - Hangul_VBase;
|
||||
if (c1 < Hangul_VCount) {
|
||||
/* compose the Hangul char */
|
||||
cc = (c0 - Hangul_LBase) * Hangul_VCount;
|
||||
cc = (cc + c1) * Hangul_TCount;
|
||||
cc += Hangul_SBase;
|
||||
ip++;
|
||||
ustrlen--;
|
||||
if (!ustrlen)
|
||||
goto done;
|
||||
c1 = be16_to_cpu(*ip) - Hangul_TBase;
|
||||
if (c1 > 0 && c1 < Hangul_TCount) {
|
||||
cc += c1;
|
||||
ip++;
|
||||
ustrlen--;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
while (1) {
|
||||
/* main loop for common case of not composed chars */
|
||||
if (!ustrlen)
|
||||
goto same;
|
||||
c1 = be16_to_cpu(*ip);
|
||||
if (likely(compose))
|
||||
ce1 = hfsplus_compose_lookup(
|
||||
hfsplus_compose_table, c1);
|
||||
if (ce1)
|
||||
break;
|
||||
switch (c0) {
|
||||
case 0:
|
||||
c0 = 0x2400;
|
||||
break;
|
||||
case '/':
|
||||
c0 = ':';
|
||||
break;
|
||||
}
|
||||
res = nls->uni2char(c0, op, len);
|
||||
if (res < 0) {
|
||||
if (res == -ENAMETOOLONG)
|
||||
goto out;
|
||||
*op = '?';
|
||||
res = 1;
|
||||
}
|
||||
op += res;
|
||||
len -= res;
|
||||
c0 = c1;
|
||||
ip++;
|
||||
ustrlen--;
|
||||
}
|
||||
ce2 = hfsplus_compose_lookup(ce1, c0);
|
||||
if (ce2) {
|
||||
i = 1;
|
||||
while (i < ustrlen) {
|
||||
ce1 = hfsplus_compose_lookup(ce2,
|
||||
be16_to_cpu(ip[i]));
|
||||
if (!ce1)
|
||||
break;
|
||||
i++;
|
||||
ce2 = ce1;
|
||||
}
|
||||
cc = ce2[0];
|
||||
if (cc) {
|
||||
ip += i;
|
||||
ustrlen -= i;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
same:
|
||||
switch (c0) {
|
||||
case 0:
|
||||
cc = 0x2400;
|
||||
break;
|
||||
case '/':
|
||||
cc = ':';
|
||||
break;
|
||||
default:
|
||||
cc = c0;
|
||||
}
|
||||
done:
|
||||
res = nls->uni2char(cc, op, len);
|
||||
if (res < 0) {
|
||||
if (res == -ENAMETOOLONG)
|
||||
goto out;
|
||||
*op = '?';
|
||||
res = 1;
|
||||
}
|
||||
op += res;
|
||||
len -= res;
|
||||
}
|
||||
res = 0;
|
||||
out:
|
||||
*len_p = (char *)op - astr;
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert one or more ASCII characters into a single unicode character.
|
||||
* Returns the number of ASCII characters corresponding to the unicode char.
|
||||
*/
|
||||
static inline int asc2unichar(struct super_block *sb, const char *astr, int len,
|
||||
wchar_t *uc)
|
||||
{
|
||||
int size = HFSPLUS_SB(sb)->nls->char2uni(astr, len, uc);
|
||||
if (size <= 0) {
|
||||
*uc = '?';
|
||||
size = 1;
|
||||
}
|
||||
switch (*uc) {
|
||||
case 0x2400:
|
||||
*uc = 0;
|
||||
break;
|
||||
case ':':
|
||||
*uc = '/';
|
||||
break;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/* Decomposes a single unicode character. */
|
||||
static inline u16 *decompose_unichar(wchar_t uc, int *size)
|
||||
{
|
||||
int off;
|
||||
|
||||
off = hfsplus_decompose_table[(uc >> 12) & 0xf];
|
||||
if (off == 0 || off == 0xffff)
|
||||
return NULL;
|
||||
|
||||
off = hfsplus_decompose_table[off + ((uc >> 8) & 0xf)];
|
||||
if (!off)
|
||||
return NULL;
|
||||
|
||||
off = hfsplus_decompose_table[off + ((uc >> 4) & 0xf)];
|
||||
if (!off)
|
||||
return NULL;
|
||||
|
||||
off = hfsplus_decompose_table[off + (uc & 0xf)];
|
||||
*size = off & 3;
|
||||
if (*size == 0)
|
||||
return NULL;
|
||||
return hfsplus_decompose_table + (off / 4);
|
||||
}
|
||||
|
||||
int hfsplus_asc2uni(struct super_block *sb,
|
||||
struct hfsplus_unistr *ustr, int max_unistr_len,
|
||||
const char *astr, int len)
|
||||
{
|
||||
int size, dsize, decompose;
|
||||
u16 *dstr, outlen = 0;
|
||||
wchar_t c;
|
||||
|
||||
decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
||||
while (outlen < max_unistr_len && len > 0) {
|
||||
size = asc2unichar(sb, astr, len, &c);
|
||||
|
||||
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++);
|
||||
} while (--dsize > 0);
|
||||
} else
|
||||
ustr->unicode[outlen++] = cpu_to_be16(c);
|
||||
|
||||
astr += size;
|
||||
len -= size;
|
||||
}
|
||||
ustr->length = cpu_to_be16(outlen);
|
||||
if (len > 0)
|
||||
return -ENAMETOOLONG;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hash a string to an integer as appropriate for the HFS+ filesystem.
|
||||
* Composed unicode characters are decomposed and case-folding is performed
|
||||
* if the appropriate bits are (un)set on the superblock.
|
||||
*/
|
||||
int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str)
|
||||
{
|
||||
struct super_block *sb = dentry->d_sb;
|
||||
const char *astr;
|
||||
const u16 *dstr;
|
||||
int casefold, decompose, size, len;
|
||||
unsigned long hash;
|
||||
wchar_t c;
|
||||
u16 c2;
|
||||
|
||||
casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
|
||||
decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
||||
hash = init_name_hash();
|
||||
astr = str->name;
|
||||
len = str->len;
|
||||
while (len > 0) {
|
||||
int uninitialized_var(dsize);
|
||||
size = asc2unichar(sb, astr, len, &c);
|
||||
astr += size;
|
||||
len -= size;
|
||||
|
||||
if (decompose)
|
||||
dstr = decompose_unichar(c, &dsize);
|
||||
else
|
||||
dstr = NULL;
|
||||
if (dstr) {
|
||||
do {
|
||||
c2 = *dstr++;
|
||||
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)
|
||||
hash = partial_name_hash(c2, hash);
|
||||
}
|
||||
}
|
||||
str->hash = end_name_hash(hash);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare strings with HFS+ filename ordering.
|
||||
* Composed unicode characters are decomposed and case-folding is performed
|
||||
* if the appropriate bits are (un)set on the superblock.
|
||||
*/
|
||||
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 = parent->d_sb;
|
||||
int casefold, decompose, size;
|
||||
int dsize1, dsize2, len1, len2;
|
||||
const u16 *dstr1, *dstr2;
|
||||
const char *astr1, *astr2;
|
||||
u16 c1, c2;
|
||||
wchar_t c;
|
||||
|
||||
casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
|
||||
decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
||||
astr1 = str;
|
||||
len1 = len;
|
||||
astr2 = name->name;
|
||||
len2 = name->len;
|
||||
dsize1 = dsize2 = 0;
|
||||
dstr1 = dstr2 = NULL;
|
||||
|
||||
while (len1 > 0 && len2 > 0) {
|
||||
if (!dsize1) {
|
||||
size = asc2unichar(sb, astr1, len1, &c);
|
||||
astr1 += size;
|
||||
len1 -= size;
|
||||
|
||||
if (decompose)
|
||||
dstr1 = decompose_unichar(c, &dsize1);
|
||||
if (!decompose || !dstr1) {
|
||||
c1 = c;
|
||||
dstr1 = &c1;
|
||||
dsize1 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dsize2) {
|
||||
size = asc2unichar(sb, astr2, len2, &c);
|
||||
astr2 += size;
|
||||
len2 -= size;
|
||||
|
||||
if (decompose)
|
||||
dstr2 = decompose_unichar(c, &dsize2);
|
||||
if (!decompose || !dstr2) {
|
||||
c2 = c;
|
||||
dstr2 = &c2;
|
||||
dsize2 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
c1 = *dstr1;
|
||||
c2 = *dstr2;
|
||||
if (casefold) {
|
||||
c1 = case_fold(c1);
|
||||
if (!c1) {
|
||||
dstr1++;
|
||||
dsize1--;
|
||||
continue;
|
||||
}
|
||||
c2 = case_fold(c2);
|
||||
if (!c2) {
|
||||
dstr2++;
|
||||
dsize2--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (c1 < c2)
|
||||
return -1;
|
||||
else if (c1 > c2)
|
||||
return 1;
|
||||
|
||||
dstr1++;
|
||||
dsize1--;
|
||||
dstr2++;
|
||||
dsize2--;
|
||||
}
|
||||
|
||||
if (len1 < len2)
|
||||
return -1;
|
||||
if (len1 > len2)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
261
fs/hfsplus/wrapper.c
Normal file
261
fs/hfsplus/wrapper.c
Normal file
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* linux/fs/hfsplus/wrapper.c
|
||||
*
|
||||
* Copyright (C) 2001
|
||||
* Brad Boyer (flar@allandria.com)
|
||||
* (C) 2003 Ardis Technologies <roman@ardistech.com>
|
||||
*
|
||||
* Handling of HFS wrappers around HFS+ volumes
|
||||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/cdrom.h>
|
||||
#include <linux/genhd.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
#include "hfsplus_fs.h"
|
||||
#include "hfsplus_raw.h"
|
||||
|
||||
struct hfsplus_wd {
|
||||
u32 ablk_size;
|
||||
u16 ablk_start;
|
||||
u16 embed_start;
|
||||
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;
|
||||
u16 attrib;
|
||||
__be16 sig;
|
||||
|
||||
sig = *(__be16 *)(bufptr + HFSP_WRAPOFF_EMBEDSIG);
|
||||
if (sig != cpu_to_be16(HFSPLUS_VOLHEAD_SIG) &&
|
||||
sig != cpu_to_be16(HFSPLUS_VOLHEAD_SIGX))
|
||||
return 0;
|
||||
|
||||
attrib = be16_to_cpu(*(__be16 *)(bufptr + HFSP_WRAPOFF_ATTRIB));
|
||||
if (!(attrib & HFSP_WRAP_ATTRIB_SLOCK) ||
|
||||
!(attrib & HFSP_WRAP_ATTRIB_SPARED))
|
||||
return 0;
|
||||
|
||||
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));
|
||||
|
||||
extent = get_unaligned_be32(bufptr + HFSP_WRAPOFF_EMBEDEXT);
|
||||
wd->embed_start = (extent >> 16) & 0xFFFF;
|
||||
wd->embed_count = extent & 0xFFFF;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int hfsplus_get_last_session(struct super_block *sb,
|
||||
sector_t *start, sector_t *size)
|
||||
{
|
||||
struct cdrom_multisession ms_info;
|
||||
struct cdrom_tocentry te;
|
||||
int res;
|
||||
|
||||
/* default values */
|
||||
*start = 0;
|
||||
*size = sb->s_bdev->bd_inode->i_size >> 9;
|
||||
|
||||
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);
|
||||
if (!res && (te.cdte_ctrl & CDROM_DATA_TRACK) == 4) {
|
||||
*start = (sector_t)te.cdte_addr.lba << 2;
|
||||
return 0;
|
||||
}
|
||||
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);
|
||||
if (!res && ms_info.xa_flag)
|
||||
*start = (sector_t)ms_info.addr.lba << 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find the volume header and fill in some minimum bits in superblock */
|
||||
/* Takes in super block, returns true if good data read */
|
||||
int hfsplus_read_wrapper(struct super_block *sb)
|
||||
{
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
|
||||
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)
|
||||
goto out;
|
||||
|
||||
if (hfsplus_get_last_session(sb, &part_start, &part_size))
|
||||
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;
|
||||
|
||||
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))
|
||||
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(sbi->s_vhdr->blocksize);
|
||||
|
||||
/*
|
||||
* Block size must be at least as large as a sector and a multiple of 2.
|
||||
*/
|
||||
if (blocksize < HFSPLUS_SECTOR_SIZE || ((blocksize - 1) & blocksize))
|
||||
goto out_free_backup_vhdr;
|
||||
sbi->alloc_blksz = blocksize;
|
||||
sbi->alloc_blksz_shift = ilog2(blocksize);
|
||||
blocksize = min_t(u32, sbi->alloc_blksz, PAGE_SIZE);
|
||||
|
||||
/*
|
||||
* Align block size to block offset.
|
||||
*/
|
||||
while (part_start & ((blocksize >> HFSPLUS_SECTOR_SHIFT) - 1))
|
||||
blocksize >>= 1;
|
||||
|
||||
if (sb_set_blocksize(sb, blocksize) != blocksize) {
|
||||
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;
|
||||
return 0;
|
||||
|
||||
out_free_backup_vhdr:
|
||||
kfree(sbi->s_backup_vhdr_buf);
|
||||
out_free_vhdr:
|
||||
kfree(sbi->s_vhdr_buf);
|
||||
out:
|
||||
return error;
|
||||
}
|
865
fs/hfsplus/xattr.c
Normal file
865
fs/hfsplus/xattr.c
Normal file
|
@ -0,0 +1,865 @@
|
|||
/*
|
||||
* 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)
|
||||
{
|
||||
if (!strcmp(name, ""))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Don't allow retrieving properly prefixed attributes
|
||||
* by prepending them with "osx."
|
||||
*/
|
||||
if (is_known_namespace(name))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/*
|
||||
* osx is the namespace we use to indicate an unprefixed
|
||||
* attribute on the filesystem (like the ones that OS X
|
||||
* creates), so we pass the name through unmodified (after
|
||||
* ensuring it doesn't conflict with another namespace).
|
||||
*/
|
||||
return hfsplus_getxattr(dentry, name, buffer, size);
|
||||
}
|
||||
|
||||
static int hfsplus_osx_setxattr(struct dentry *dentry, const char *name,
|
||||
const void *buffer, size_t size, int flags, int type)
|
||||
{
|
||||
if (!strcmp(name, ""))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Don't allow setting properly prefixed attributes
|
||||
* by prepending them with "osx."
|
||||
*/
|
||||
if (is_known_namespace(name))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/*
|
||||
* osx is the namespace we use to indicate an unprefixed
|
||||
* attribute on the filesystem (like the ones that OS X
|
||||
* creates), so we pass the name through unmodified (after
|
||||
* ensuring it doesn't conflict with another namespace).
|
||||
*/
|
||||
return hfsplus_setxattr(dentry, name, buffer, size, flags);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
49
fs/hfsplus/xattr.h
Normal file
49
fs/hfsplus/xattr.h
Normal file
|
@ -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
|
124
fs/hfsplus/xattr_security.c
Normal file
124
fs/hfsplus/xattr_security.c
Normal file
|
@ -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,
|
||||
};
|
71
fs/hfsplus/xattr_trusted.c
Normal file
71
fs/hfsplus/xattr_trusted.c
Normal file
|
@ -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,
|
||||
};
|
71
fs/hfsplus/xattr_user.c
Normal file
71
fs/hfsplus/xattr_user.c
Normal file
|
@ -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,
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue