mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
Merge tag 'hfs-v7.1-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/vdubeyko/hfs
Pull hfsplus updates from Viacheslav Dubeyko:
"This contains several fixes of syzbot reported issues and HFS+ fixes
of xfstests failures.
- Fix a syzbot reported issue of a KMSAN uninit-value in
hfsplus_strcasecmp().
The root cause was that hfs_brec_read() doesn't validate that the
on-disk record size matches the expected size for the record type
being read. The fix introduced hfsplus_brec_read_cat() wrapper that
validates the record size based on the type field and returns -EIO
if size doesn't match (Deepanshu Kartikey)
- Fix a syzbot reported issue of processing corrupted HFS+ images
where the b-tree allocation bitmap indicates that the header node
(Node 0) is free. Node 0 must always be allocated. Violating this
invariant leads to allocator corruption, which cascades into kernel
panics or undefined behavior.
Prevent trusting a corrupted allocator state by adding a validation
check during hfs_btree_open(). If corruption is detected, print a
warning identifying the specific corrupted tree and force the
filesystem to mount read-only (SB_RDONLY).
This prevents kernel panics from corrupted images while enabling
data recovery (Shardul Bankar)
- Fix a potential deadlock in hfsplus_fill_super().
hfsplus_fill_super() calls hfs_find_init() to initialize a search
structure, which acquires tree->tree_lock. If the subsequent call
to hfsplus_cat_build_key() fails, the function jumps to the
out_put_root error label without releasing the lock.
Fix this by adding the missing hfs_find_exit(&fd) call before
jumping to the out_put_root error label. This ensures that
tree->tree_lock is properly released on the error path (Zilin Guan)
- Update a files ctime after rename in hfsplus_rename() (Yangtao Li)
The rest of the patches introduce the HFS+ fixes for the case of
generic/348, generic/728, generic/533, generic/523, and generic/642
test-cases of xfstests suite"
* tag 'hfs-v7.1-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/vdubeyko/hfs:
hfsplus: fix generic/642 failure
hfsplus: rework logic of map nodes creation in xattr b-tree
hfsplus: fix logic of alloc/free b-tree node
hfsplus: fix error processing issue in hfs_bmap_free()
hfsplus: fix potential race conditions in b-tree functionality
hfsplus: extract hidden directory search into a helper function
hfsplus: fix held lock freed on hfsplus_fill_super()
hfsplus: fix generic/523 test-case failure
hfsplus: validate b-tree node 0 bitmap at mount time
hfsplus: refactor b-tree map page access and add node-type validation
hfsplus: fix to update ctime after rename
hfsplus: fix generic/533 test-case failure
hfsplus: set ctime after setxattr and removexattr
hfsplus: fix uninit-value by validating catalog record size
hfsplus: fix potential Allocation File corruption after fsync
This commit is contained in:
@@ -57,7 +57,8 @@ int hfsplus_attr_build_key(struct super_block *sb, hfsplus_btree_key *key,
|
||||
if (name) {
|
||||
int res = hfsplus_asc2uni(sb,
|
||||
(struct hfsplus_unistr *)&key->attr.key_name,
|
||||
HFSPLUS_ATTR_MAX_STRLEN, name, strlen(name));
|
||||
HFSPLUS_ATTR_MAX_STRLEN, name, strlen(name),
|
||||
HFS_XATTR_NAME);
|
||||
if (res)
|
||||
return res;
|
||||
len = be16_to_cpu(key->attr.key_name.length);
|
||||
@@ -153,14 +154,22 @@ int hfsplus_find_attr(struct super_block *sb, u32 cnid,
|
||||
if (err)
|
||||
goto failed_find_attr;
|
||||
err = hfs_brec_find(fd, hfs_find_rec_by_key);
|
||||
if (err)
|
||||
if (err == -ENOENT) {
|
||||
/* file exists but xattr is absent */
|
||||
err = -ENODATA;
|
||||
goto failed_find_attr;
|
||||
} else 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)
|
||||
if (err == -ENOENT) {
|
||||
/* file exists but xattr is absent */
|
||||
err = -ENODATA;
|
||||
goto failed_find_attr;
|
||||
} else if (err)
|
||||
goto failed_find_attr;
|
||||
}
|
||||
|
||||
@@ -174,6 +183,9 @@ int hfsplus_attr_exists(struct inode *inode, const char *name)
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct hfs_find_data fd;
|
||||
|
||||
hfs_dbg("name %s, ino %llu\n",
|
||||
name ? name : NULL, inode->i_ino);
|
||||
|
||||
if (!HFSPLUS_SB(sb)->attr_tree)
|
||||
return 0;
|
||||
|
||||
@@ -241,6 +253,7 @@ int hfsplus_create_attr_nolock(struct inode *inode, const char *name,
|
||||
return err;
|
||||
}
|
||||
|
||||
hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(sb), HFSPLUS_I_ATTR_DIRTY);
|
||||
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
|
||||
|
||||
return 0;
|
||||
@@ -292,15 +305,16 @@ failed_init_create_attr:
|
||||
static int __hfsplus_delete_attr(struct inode *inode, u32 cnid,
|
||||
struct hfs_find_data *fd)
|
||||
{
|
||||
int err = 0;
|
||||
int err;
|
||||
__be32 found_cnid, record_type;
|
||||
|
||||
found_cnid = U32_MAX;
|
||||
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;
|
||||
return -ENODATA;
|
||||
|
||||
hfs_bnode_read(fd->bnode, &record_type,
|
||||
fd->entryoffset, sizeof(record_type));
|
||||
@@ -326,8 +340,10 @@ static int __hfsplus_delete_attr(struct inode *inode, u32 cnid,
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(inode->i_sb),
|
||||
HFSPLUS_I_ATTR_DIRTY);
|
||||
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
@@ -351,7 +367,10 @@ int hfsplus_delete_attr_nolock(struct inode *inode, const char *name,
|
||||
}
|
||||
|
||||
err = hfs_brec_find(fd, hfs_find_rec_by_key);
|
||||
if (err)
|
||||
if (err == -ENOENT) {
|
||||
/* file exists but xattr is absent */
|
||||
return -ENODATA;
|
||||
} else if (err)
|
||||
return err;
|
||||
|
||||
err = __hfsplus_delete_attr(inode, inode->i_ino, fd);
|
||||
@@ -411,9 +430,14 @@ int hfsplus_delete_all_attrs(struct inode *dir, u32 cnid)
|
||||
|
||||
for (;;) {
|
||||
err = hfsplus_find_attr(dir->i_sb, cnid, NULL, &fd);
|
||||
if (err) {
|
||||
if (err != -ENOENT)
|
||||
pr_err("xattr search failed\n");
|
||||
if (err == -ENOENT || err == -ENODATA) {
|
||||
/*
|
||||
* xattr has not been found
|
||||
*/
|
||||
err = -ENODATA;
|
||||
goto end_delete_all;
|
||||
} else if (err) {
|
||||
pr_err("xattr search failed\n");
|
||||
goto end_delete_all;
|
||||
}
|
||||
|
||||
|
||||
@@ -287,3 +287,54 @@ out:
|
||||
fd->bnode = bnode;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* hfsplus_brec_read_cat - read and validate a catalog record
|
||||
* @fd: find data structure
|
||||
* @entry: pointer to catalog entry to read into
|
||||
*
|
||||
* Reads a catalog record and validates its size matches the expected
|
||||
* size based on the record type.
|
||||
*
|
||||
* Returns 0 on success, or negative error code on failure.
|
||||
*/
|
||||
int hfsplus_brec_read_cat(struct hfs_find_data *fd, hfsplus_cat_entry *entry)
|
||||
{
|
||||
int res;
|
||||
u32 expected_size;
|
||||
|
||||
res = hfs_brec_read(fd, entry, sizeof(hfsplus_cat_entry));
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
/* Validate catalog record size based on type */
|
||||
switch (be16_to_cpu(entry->type)) {
|
||||
case HFSPLUS_FOLDER:
|
||||
expected_size = sizeof(struct hfsplus_cat_folder);
|
||||
break;
|
||||
case HFSPLUS_FILE:
|
||||
expected_size = sizeof(struct hfsplus_cat_file);
|
||||
break;
|
||||
case HFSPLUS_FOLDER_THREAD:
|
||||
case HFSPLUS_FILE_THREAD:
|
||||
/* Ensure we have at least the fixed fields before reading nodeName.length */
|
||||
if (fd->entrylength < HFSPLUS_MIN_THREAD_SZ) {
|
||||
pr_err("thread record too short (got %u)\n", fd->entrylength);
|
||||
return -EIO;
|
||||
}
|
||||
expected_size = hfsplus_cat_thread_size(&entry->thread);
|
||||
break;
|
||||
default:
|
||||
pr_err("unknown catalog record type %d\n",
|
||||
be16_to_cpu(entry->type));
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (fd->entrylength != expected_size) {
|
||||
pr_err("catalog record size mismatch (type %d, got %u, expected %u)\n",
|
||||
be16_to_cpu(entry->type), fd->entrylength, expected_size);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -420,7 +420,10 @@ void hfs_bnode_unlink(struct hfs_bnode *node)
|
||||
tree->root = 0;
|
||||
tree->depth = 0;
|
||||
}
|
||||
|
||||
spin_lock(&tree->hash_lock);
|
||||
set_bit(HFS_BNODE_DELETED, &node->flags);
|
||||
spin_unlock(&tree->hash_lock);
|
||||
}
|
||||
|
||||
static inline int hfs_bnode_hash(u32 num)
|
||||
|
||||
@@ -239,6 +239,9 @@ static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
|
||||
struct hfs_bnode_desc node_desc;
|
||||
int num_recs, new_rec_off, new_off, old_rec_off;
|
||||
int data_start, data_end, size;
|
||||
size_t rec_off_tbl_size;
|
||||
size_t node_desc_size = sizeof(struct hfs_bnode_desc);
|
||||
size_t rec_size = sizeof(__be16);
|
||||
|
||||
tree = fd->tree;
|
||||
node = fd->bnode;
|
||||
@@ -265,18 +268,22 @@ static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
|
||||
return next_node;
|
||||
}
|
||||
|
||||
size = tree->node_size / 2 - node->num_recs * 2 - 14;
|
||||
old_rec_off = tree->node_size - 4;
|
||||
rec_off_tbl_size = node->num_recs * rec_size;
|
||||
size = tree->node_size / 2;
|
||||
size -= node_desc_size;
|
||||
size -= rec_off_tbl_size;
|
||||
old_rec_off = tree->node_size - (2 * rec_size);
|
||||
|
||||
num_recs = 1;
|
||||
for (;;) {
|
||||
data_start = hfs_bnode_read_u16(node, old_rec_off);
|
||||
if (data_start > size)
|
||||
break;
|
||||
old_rec_off -= 2;
|
||||
old_rec_off -= rec_size;
|
||||
if (++num_recs < node->num_recs)
|
||||
continue;
|
||||
/* panic? */
|
||||
hfs_bnode_put(node);
|
||||
hfs_bnode_unlink(new_node);
|
||||
hfs_bnode_put(new_node);
|
||||
if (next_node)
|
||||
hfs_bnode_put(next_node);
|
||||
@@ -287,7 +294,7 @@ static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
|
||||
/* new record is in the lower half,
|
||||
* so leave some more space there
|
||||
*/
|
||||
old_rec_off += 2;
|
||||
old_rec_off += rec_size;
|
||||
num_recs--;
|
||||
data_start = hfs_bnode_read_u16(node, old_rec_off);
|
||||
} else {
|
||||
@@ -295,27 +302,28 @@ static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
|
||||
hfs_bnode_get(new_node);
|
||||
fd->bnode = new_node;
|
||||
fd->record -= num_recs;
|
||||
fd->keyoffset -= data_start - 14;
|
||||
fd->entryoffset -= data_start - 14;
|
||||
fd->keyoffset -= data_start - node_desc_size;
|
||||
fd->entryoffset -= data_start - node_desc_size;
|
||||
}
|
||||
new_node->num_recs = node->num_recs - num_recs;
|
||||
node->num_recs = num_recs;
|
||||
|
||||
new_rec_off = tree->node_size - 2;
|
||||
new_off = 14;
|
||||
new_rec_off = tree->node_size - rec_size;
|
||||
new_off = node_desc_size;
|
||||
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;
|
||||
old_rec_off -= rec_size;
|
||||
new_rec_off -= rec_size;
|
||||
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);
|
||||
hfs_bnode_copy(new_node, node_desc_size,
|
||||
node, data_start, data_end - data_start);
|
||||
|
||||
/* update new bnode header */
|
||||
node_desc.next = cpu_to_be32(new_node->next);
|
||||
|
||||
@@ -129,12 +129,148 @@ u32 hfsplus_calc_btree_clump_size(u32 block_size, u32 node_size,
|
||||
return clump_size;
|
||||
}
|
||||
|
||||
/* Context for iterating b-tree map pages
|
||||
* @page_idx: The index of the page within the b-node's page array
|
||||
* @off: The byte offset within the mapped page
|
||||
* @len: The remaining length of the map record
|
||||
*/
|
||||
struct hfs_bmap_ctx {
|
||||
unsigned int page_idx;
|
||||
unsigned int off;
|
||||
u16 len;
|
||||
};
|
||||
|
||||
/*
|
||||
* Finds the specific page containing the requested byte offset within the map
|
||||
* record. Automatically handles the difference between header and map nodes.
|
||||
* Returns the struct page pointer, or an ERR_PTR on failure.
|
||||
* Note: The caller is responsible for mapping/unmapping the returned page.
|
||||
*/
|
||||
static struct page *hfs_bmap_get_map_page(struct hfs_bnode *node,
|
||||
struct hfs_bmap_ctx *ctx,
|
||||
u32 byte_offset)
|
||||
{
|
||||
u16 rec_idx, off16;
|
||||
unsigned int page_off;
|
||||
|
||||
if (node->this == HFSPLUS_TREE_HEAD) {
|
||||
if (node->type != HFS_NODE_HEADER) {
|
||||
pr_err("hfsplus: invalid btree header node\n");
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
rec_idx = HFSPLUS_BTREE_HDR_MAP_REC_INDEX;
|
||||
} else {
|
||||
if (node->type != HFS_NODE_MAP) {
|
||||
pr_err("hfsplus: invalid btree map node\n");
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
rec_idx = HFSPLUS_BTREE_MAP_NODE_REC_INDEX;
|
||||
}
|
||||
|
||||
ctx->len = hfs_brec_lenoff(node, rec_idx, &off16);
|
||||
if (!ctx->len)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
if (!is_bnode_offset_valid(node, off16))
|
||||
return ERR_PTR(-EIO);
|
||||
|
||||
ctx->len = check_and_correct_requested_length(node, off16, ctx->len);
|
||||
|
||||
if (byte_offset >= ctx->len)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
page_off = (u32)off16 + node->page_offset + byte_offset;
|
||||
ctx->page_idx = page_off >> PAGE_SHIFT;
|
||||
ctx->off = page_off & ~PAGE_MASK;
|
||||
|
||||
return node->page[ctx->page_idx];
|
||||
}
|
||||
|
||||
/**
|
||||
* hfs_bmap_test_bit - test a bit in the b-tree map
|
||||
* @node: the b-tree node containing the map record
|
||||
* @node_bit_idx: the relative bit index within the node's map record
|
||||
*
|
||||
* Returns true if set, false if clear or on failure.
|
||||
*/
|
||||
static bool hfs_bmap_test_bit(struct hfs_bnode *node, u32 node_bit_idx)
|
||||
{
|
||||
struct hfs_bmap_ctx ctx;
|
||||
struct page *page;
|
||||
u8 *bmap, byte, mask;
|
||||
|
||||
page = hfs_bmap_get_map_page(node, &ctx, node_bit_idx / BITS_PER_BYTE);
|
||||
if (IS_ERR(page))
|
||||
return false;
|
||||
|
||||
bmap = kmap_local_page(page);
|
||||
byte = bmap[ctx.off];
|
||||
kunmap_local(bmap);
|
||||
|
||||
mask = 1 << (7 - (node_bit_idx % BITS_PER_BYTE));
|
||||
return (byte & mask) != 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* hfs_bmap_clear_bit - clear a bit in the b-tree map
|
||||
* @node: the b-tree node containing the map record
|
||||
* @node_bit_idx: the relative bit index within the node's map record
|
||||
*
|
||||
* Returns 0 on success, -EINVAL if already clear, or negative error code.
|
||||
*/
|
||||
static int hfs_bmap_clear_bit(struct hfs_bnode *node, u32 node_bit_idx)
|
||||
{
|
||||
struct hfs_bmap_ctx ctx;
|
||||
struct page *page;
|
||||
u8 *bmap, mask;
|
||||
|
||||
page = hfs_bmap_get_map_page(node, &ctx, node_bit_idx / BITS_PER_BYTE);
|
||||
if (IS_ERR(page))
|
||||
return PTR_ERR(page);
|
||||
|
||||
bmap = kmap_local_page(page);
|
||||
|
||||
mask = 1 << (7 - (node_bit_idx % BITS_PER_BYTE));
|
||||
|
||||
if (!(bmap[ctx.off] & mask)) {
|
||||
kunmap_local(bmap);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bmap[ctx.off] &= ~mask;
|
||||
set_page_dirty(page);
|
||||
kunmap_local(bmap);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define HFS_EXTENT_TREE_NAME "Extents Overflow File"
|
||||
#define HFS_CATALOG_TREE_NAME "Catalog File"
|
||||
#define HFS_ATTR_TREE_NAME "Attributes File"
|
||||
#define HFS_UNKNOWN_TREE_NAME "Unknown B-tree"
|
||||
|
||||
static const char *hfs_btree_name(u32 cnid)
|
||||
{
|
||||
switch (cnid) {
|
||||
case HFSPLUS_EXT_CNID:
|
||||
return HFS_EXTENT_TREE_NAME;
|
||||
case HFSPLUS_CAT_CNID:
|
||||
return HFS_CATALOG_TREE_NAME;
|
||||
case HFSPLUS_ATTR_CNID:
|
||||
return HFS_ATTR_TREE_NAME;
|
||||
default:
|
||||
return HFS_UNKNOWN_TREE_NAME;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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 hfs_bnode *node;
|
||||
struct inode *inode;
|
||||
struct page *page;
|
||||
unsigned int size;
|
||||
@@ -242,6 +378,20 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id)
|
||||
|
||||
kunmap_local(head);
|
||||
put_page(page);
|
||||
|
||||
node = hfs_bnode_find(tree, HFSPLUS_TREE_HEAD);
|
||||
if (IS_ERR(node))
|
||||
goto free_inode;
|
||||
|
||||
if (!hfs_bmap_test_bit(node, 0)) {
|
||||
pr_warn("(%s): %s (cnid 0x%x) map record invalid or bitmap corruption detected, forcing read-only.\n",
|
||||
sb->s_id, hfs_btree_name(id), id);
|
||||
pr_warn("Run fsck.hfsplus to repair.\n");
|
||||
sb->s_flags |= SB_RDONLY;
|
||||
}
|
||||
|
||||
hfs_bnode_put(node);
|
||||
|
||||
return tree;
|
||||
|
||||
fail_page:
|
||||
@@ -351,6 +501,8 @@ int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes)
|
||||
u32 count;
|
||||
int res;
|
||||
|
||||
lockdep_assert_held(&tree->tree_lock);
|
||||
|
||||
if (rsvd_nodes <= 0)
|
||||
return 0;
|
||||
|
||||
@@ -374,14 +526,14 @@ int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes)
|
||||
struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
|
||||
{
|
||||
struct hfs_bnode *node, *next_node;
|
||||
struct page **pagep;
|
||||
struct hfs_bmap_ctx ctx;
|
||||
struct page *page;
|
||||
u32 nidx, idx;
|
||||
unsigned off;
|
||||
u16 off16;
|
||||
u16 len;
|
||||
u8 *data, byte, m;
|
||||
int i, res;
|
||||
|
||||
lockdep_assert_held(&tree->tree_lock);
|
||||
|
||||
res = hfs_bmap_reserve(tree, 1);
|
||||
if (res)
|
||||
return ERR_PTR(res);
|
||||
@@ -390,32 +542,29 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
|
||||
node = hfs_bnode_find(tree, nidx);
|
||||
if (IS_ERR(node))
|
||||
return node;
|
||||
len = hfs_brec_lenoff(node, 2, &off16);
|
||||
off = off16;
|
||||
|
||||
if (!is_bnode_offset_valid(node, off)) {
|
||||
page = hfs_bmap_get_map_page(node, &ctx, 0);
|
||||
if (IS_ERR(page)) {
|
||||
res = PTR_ERR(page);
|
||||
hfs_bnode_put(node);
|
||||
return ERR_PTR(-EIO);
|
||||
return ERR_PTR(res);
|
||||
}
|
||||
len = check_and_correct_requested_length(node, off, len);
|
||||
|
||||
off += node->page_offset;
|
||||
pagep = node->page + (off >> PAGE_SHIFT);
|
||||
data = kmap_local_page(*pagep);
|
||||
off &= ~PAGE_MASK;
|
||||
data = kmap_local_page(page);
|
||||
idx = 0;
|
||||
|
||||
for (;;) {
|
||||
while (len) {
|
||||
byte = data[off];
|
||||
while (ctx.len) {
|
||||
byte = data[ctx.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);
|
||||
data[ctx.off] |= m;
|
||||
set_page_dirty(page);
|
||||
kunmap_local(data);
|
||||
tree->free_nodes--;
|
||||
hfs_btree_write(tree);
|
||||
mark_inode_dirty(tree->inode);
|
||||
hfs_bnode_put(node);
|
||||
return hfs_bnode_create(tree,
|
||||
@@ -423,19 +572,21 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (++off >= PAGE_SIZE) {
|
||||
if (++ctx.off >= PAGE_SIZE) {
|
||||
kunmap_local(data);
|
||||
data = kmap_local_page(*++pagep);
|
||||
off = 0;
|
||||
page = node->page[++ctx.page_idx];
|
||||
data = kmap_local_page(page);
|
||||
ctx.off = 0;
|
||||
}
|
||||
idx += 8;
|
||||
len--;
|
||||
ctx.len--;
|
||||
}
|
||||
kunmap_local(data);
|
||||
nidx = node->next;
|
||||
if (!nidx) {
|
||||
hfs_dbg("create new bmap node\n");
|
||||
next_node = hfs_bmap_new_bmap(node, idx);
|
||||
hfs_btree_write(tree);
|
||||
} else
|
||||
next_node = hfs_bnode_find(tree, nidx);
|
||||
hfs_bnode_put(node);
|
||||
@@ -443,26 +594,27 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
|
||||
return next_node;
|
||||
node = next_node;
|
||||
|
||||
len = hfs_brec_lenoff(node, 0, &off16);
|
||||
off = off16;
|
||||
off += node->page_offset;
|
||||
pagep = node->page + (off >> PAGE_SHIFT);
|
||||
data = kmap_local_page(*pagep);
|
||||
off &= ~PAGE_MASK;
|
||||
page = hfs_bmap_get_map_page(node, &ctx, 0);
|
||||
if (IS_ERR(page)) {
|
||||
res = PTR_ERR(page);
|
||||
hfs_bnode_put(node);
|
||||
return ERR_PTR(res);
|
||||
}
|
||||
data = kmap_local_page(page);
|
||||
}
|
||||
}
|
||||
|
||||
void hfs_bmap_free(struct hfs_bnode *node)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
struct page *page;
|
||||
u16 off, len;
|
||||
u32 nidx;
|
||||
u8 *data, byte, m;
|
||||
int res;
|
||||
|
||||
hfs_dbg("node %u\n", node->this);
|
||||
BUG_ON(!node->this);
|
||||
tree = node->tree;
|
||||
lockdep_assert_held(&tree->tree_lock);
|
||||
nidx = node->this;
|
||||
node = hfs_bnode_find(tree, 0);
|
||||
if (IS_ERR(node))
|
||||
@@ -495,24 +647,19 @@ void hfs_bmap_free(struct hfs_bnode *node)
|
||||
}
|
||||
len = hfs_brec_lenoff(node, 0, &off);
|
||||
}
|
||||
off += node->page_offset + nidx / 8;
|
||||
page = node->page[off >> PAGE_SHIFT];
|
||||
data = kmap_local_page(page);
|
||||
off &= ~PAGE_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_local(data);
|
||||
hfs_bnode_put(node);
|
||||
return;
|
||||
|
||||
res = hfs_bmap_clear_bit(node, nidx);
|
||||
if (res == -EINVAL) {
|
||||
pr_crit("trying to free the freed bnode %u(%d)\n",
|
||||
nidx, node->type);
|
||||
} else if (res) {
|
||||
pr_crit("fail to free bnode %u(%d)\n",
|
||||
nidx, node->type);
|
||||
} else {
|
||||
tree->free_nodes++;
|
||||
hfs_btree_write(tree);
|
||||
mark_inode_dirty(tree->inode);
|
||||
}
|
||||
data[off] = byte & ~m;
|
||||
set_page_dirty(page);
|
||||
kunmap_local(data);
|
||||
|
||||
hfs_bnode_put(node);
|
||||
tree->free_nodes++;
|
||||
mark_inode_dirty(tree->inode);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ int hfsplus_cat_build_key(struct super_block *sb,
|
||||
|
||||
key->cat.parent = cpu_to_be32(parent);
|
||||
err = hfsplus_asc2uni(sb, &key->cat.name, HFSPLUS_MAX_STRLEN,
|
||||
str->name, str->len);
|
||||
str->name, str->len, HFS_REGULAR_NAME);
|
||||
if (unlikely(err < 0))
|
||||
return err;
|
||||
|
||||
@@ -183,7 +183,7 @@ static int hfsplus_fill_cat_thread(struct super_block *sb,
|
||||
entry->thread.reserved = 0;
|
||||
entry->thread.parentID = cpu_to_be32(parentid);
|
||||
err = hfsplus_asc2uni(sb, &entry->thread.nodeName, HFSPLUS_MAX_STRLEN,
|
||||
str->name, str->len);
|
||||
str->name, str->len, HFS_REGULAR_NAME);
|
||||
if (unlikely(err < 0))
|
||||
return err;
|
||||
|
||||
@@ -194,12 +194,12 @@ static int hfsplus_fill_cat_thread(struct super_block *sb,
|
||||
int hfsplus_find_cat(struct super_block *sb, u32 cnid,
|
||||
struct hfs_find_data *fd)
|
||||
{
|
||||
hfsplus_cat_entry tmp;
|
||||
hfsplus_cat_entry tmp = {0};
|
||||
int err;
|
||||
u16 type;
|
||||
|
||||
hfsplus_cat_build_key_with_cnid(sb, fd->search_key, cnid);
|
||||
err = hfs_brec_read(fd, &tmp, sizeof(hfsplus_cat_entry));
|
||||
err = hfsplus_brec_read_cat(fd, &tmp);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -313,6 +313,7 @@ int hfsplus_create_cat(u32 cnid, struct inode *dir,
|
||||
if (S_ISDIR(inode->i_mode))
|
||||
hfsplus_subfolders_inc(dir);
|
||||
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
|
||||
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(sb), HFSPLUS_I_CAT_DIRTY);
|
||||
hfsplus_mark_inode_dirty(dir, HFSPLUS_I_CAT_DIRTY);
|
||||
|
||||
hfs_find_exit(&fd);
|
||||
@@ -418,6 +419,7 @@ int hfsplus_delete_cat(u32 cnid, struct inode *dir, const struct qstr *str)
|
||||
if (type == HFSPLUS_FOLDER)
|
||||
hfsplus_subfolders_dec(dir);
|
||||
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
|
||||
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(sb), HFSPLUS_I_CAT_DIRTY);
|
||||
hfsplus_mark_inode_dirty(dir, HFSPLUS_I_CAT_DIRTY);
|
||||
|
||||
if (type == HFSPLUS_FILE || type == HFSPLUS_FOLDER) {
|
||||
@@ -540,6 +542,7 @@ int hfsplus_rename_cat(u32 cnid,
|
||||
}
|
||||
err = hfs_brec_insert(&dst_fd, &entry, entry_size);
|
||||
|
||||
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(sb), HFSPLUS_I_CAT_DIRTY);
|
||||
hfsplus_mark_inode_dirty(dst_dir, HFSPLUS_I_CAT_DIRTY);
|
||||
hfsplus_mark_inode_dirty(src_dir, HFSPLUS_I_CAT_DIRTY);
|
||||
out:
|
||||
|
||||
@@ -49,7 +49,7 @@ static struct dentry *hfsplus_lookup(struct inode *dir, struct dentry *dentry,
|
||||
if (unlikely(err < 0))
|
||||
goto fail;
|
||||
again:
|
||||
err = hfs_brec_read(&fd, &entry, sizeof(entry));
|
||||
err = hfsplus_brec_read_cat(&fd, &entry);
|
||||
if (err) {
|
||||
if (err == -ENOENT) {
|
||||
hfs_find_exit(&fd);
|
||||
@@ -478,6 +478,9 @@ static int hfsplus_symlink(struct mnt_idmap *idmap, struct inode *dir,
|
||||
if (!inode)
|
||||
goto out;
|
||||
|
||||
hfs_dbg("dir->i_ino %llu, inode->i_ino %llu\n",
|
||||
dir->i_ino, inode->i_ino);
|
||||
|
||||
res = page_symlink(inode, symname, strlen(symname) + 1);
|
||||
if (res)
|
||||
goto out_err;
|
||||
@@ -526,6 +529,9 @@ static int hfsplus_mknod(struct mnt_idmap *idmap, struct inode *dir,
|
||||
if (!inode)
|
||||
goto out;
|
||||
|
||||
hfs_dbg("dir->i_ino %llu, inode->i_ino %llu\n",
|
||||
dir->i_ino, inode->i_ino);
|
||||
|
||||
if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode))
|
||||
init_special_inode(inode, mode, rdev);
|
||||
|
||||
@@ -597,11 +603,22 @@ static int hfsplus_rename(struct mnt_idmap *idmap,
|
||||
old_dir, &old_dentry->d_name,
|
||||
new_dir, &new_dentry->d_name);
|
||||
if (!res) {
|
||||
struct inode *inode = d_inode(old_dentry);
|
||||
|
||||
new_dentry->d_fsdata = old_dentry->d_fsdata;
|
||||
|
||||
inode_set_ctime_current(inode);
|
||||
mark_inode_dirty(inode);
|
||||
|
||||
res = hfsplus_cat_write_inode(old_dir);
|
||||
if (!res)
|
||||
res = hfsplus_cat_write_inode(new_dir);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
res = hfsplus_cat_write_inode(new_dir);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
res = hfsplus_cat_write_inode(inode);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -121,6 +121,8 @@ static int __hfsplus_ext_write_extent(struct inode *inode,
|
||||
* redirty the inode. Instead the callers have to be careful
|
||||
* to explicily mark the inode dirty, too.
|
||||
*/
|
||||
set_bit(HFSPLUS_I_EXT_DIRTY,
|
||||
&HFSPLUS_I(HFSPLUS_EXT_TREE_I(inode->i_sb))->flags);
|
||||
set_bit(HFSPLUS_I_EXT_DIRTY, &hip->flags);
|
||||
|
||||
return 0;
|
||||
@@ -513,6 +515,8 @@ out:
|
||||
if (!res) {
|
||||
hip->alloc_blocks += len;
|
||||
mutex_unlock(&hip->extents_lock);
|
||||
hfsplus_mark_inode_dirty(HFSPLUS_SB(sb)->alloc_file,
|
||||
HFSPLUS_I_ALLOC_DIRTY);
|
||||
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ALLOC_DIRTY);
|
||||
return 0;
|
||||
}
|
||||
@@ -582,6 +586,7 @@ void hfsplus_file_truncate(struct inode *inode)
|
||||
/* XXX: We lack error handling of hfsplus_file_truncate() */
|
||||
return;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
if (alloc_cnt == hip->first_blocks) {
|
||||
mutex_unlock(&fd.tree->tree_lock);
|
||||
@@ -623,5 +628,7 @@ out_unlock:
|
||||
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(HFSPLUS_SB(sb)->alloc_file,
|
||||
HFSPLUS_I_ALLOC_DIRTY);
|
||||
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ALLOC_DIRTY);
|
||||
}
|
||||
|
||||
@@ -238,6 +238,13 @@ static inline struct hfsplus_inode_info *HFSPLUS_I(struct inode *inode)
|
||||
return container_of(inode, struct hfsplus_inode_info, vfs_inode);
|
||||
}
|
||||
|
||||
#define HFSPLUS_CAT_TREE_I(sb) \
|
||||
HFSPLUS_SB(sb)->cat_tree->inode
|
||||
#define HFSPLUS_EXT_TREE_I(sb) \
|
||||
HFSPLUS_SB(sb)->ext_tree->inode
|
||||
#define HFSPLUS_ATTR_TREE_I(sb) \
|
||||
HFSPLUS_SB(sb)->attr_tree->inode
|
||||
|
||||
/*
|
||||
* Mark an inode dirty, and also mark the btree in which the
|
||||
* specific type of metadata is stored.
|
||||
@@ -499,7 +506,8 @@ int hfsplus_uni2asc_xattr_str(struct super_block *sb,
|
||||
const struct hfsplus_attr_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 max_unistr_len, const char *astr, int len,
|
||||
int name_type);
|
||||
int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str);
|
||||
int hfsplus_compare_dentry(const struct dentry *dentry, unsigned int len,
|
||||
const char *str, const struct qstr *name);
|
||||
@@ -509,6 +517,15 @@ int hfsplus_submit_bio(struct super_block *sb, sector_t sector, void *buf,
|
||||
void **data, blk_opf_t opf);
|
||||
int hfsplus_read_wrapper(struct super_block *sb);
|
||||
|
||||
static inline u32 hfsplus_cat_thread_size(const struct hfsplus_cat_thread *thread)
|
||||
{
|
||||
return offsetof(struct hfsplus_cat_thread, nodeName) +
|
||||
offsetof(struct hfsplus_unistr, unicode) +
|
||||
be16_to_cpu(thread->nodeName.length) * sizeof(hfsplus_unichr);
|
||||
}
|
||||
|
||||
int hfsplus_brec_read_cat(struct hfs_find_data *fd, hfsplus_cat_entry *entry);
|
||||
|
||||
/*
|
||||
* time helpers: convert between 1904-base and 1970-base timestamps
|
||||
*
|
||||
@@ -555,7 +572,12 @@ hfsplus_btree_lock_class(struct hfs_btree *tree)
|
||||
static inline
|
||||
bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)
|
||||
{
|
||||
bool is_valid = off < node->tree->node_size;
|
||||
bool is_valid;
|
||||
|
||||
if (!node || !node->tree)
|
||||
return false;
|
||||
|
||||
is_valid = off < node->tree->node_size;
|
||||
|
||||
if (!is_valid) {
|
||||
pr_err("requested invalid offset: "
|
||||
|
||||
@@ -324,6 +324,7 @@ int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,
|
||||
{
|
||||
struct inode *inode = file->f_mapping->host;
|
||||
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
|
||||
struct hfsplus_vh *vhdr = sbi->s_vhdr;
|
||||
int error = 0, error2;
|
||||
@@ -344,29 +345,39 @@ int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,
|
||||
/*
|
||||
* And explicitly write out the btrees.
|
||||
*/
|
||||
if (test_and_clear_bit(HFSPLUS_I_CAT_DIRTY, &hip->flags))
|
||||
if (test_and_clear_bit(HFSPLUS_I_CAT_DIRTY,
|
||||
&HFSPLUS_I(HFSPLUS_CAT_TREE_I(sb))->flags)) {
|
||||
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)) {
|
||||
if (test_and_clear_bit(HFSPLUS_I_EXT_DIRTY,
|
||||
&HFSPLUS_I(HFSPLUS_EXT_TREE_I(sb))->flags)) {
|
||||
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) {
|
||||
if (sbi->attr_tree) {
|
||||
if (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY,
|
||||
&HFSPLUS_I(HFSPLUS_ATTR_TREE_I(sb))->flags)) {
|
||||
clear_bit(HFSPLUS_I_ATTR_DIRTY, &hip->flags);
|
||||
error2 =
|
||||
filemap_write_and_wait(
|
||||
sbi->attr_tree->inode->i_mapping);
|
||||
if (!error)
|
||||
error = error2;
|
||||
} else {
|
||||
pr_err("sync non-existent attributes tree\n");
|
||||
}
|
||||
} else {
|
||||
if (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY, &hip->flags))
|
||||
pr_err("sync non-existent attributes tree\n");
|
||||
}
|
||||
|
||||
if (test_and_clear_bit(HFSPLUS_I_ALLOC_DIRTY, &hip->flags)) {
|
||||
if (test_and_clear_bit(HFSPLUS_I_ALLOC_DIRTY,
|
||||
&HFSPLUS_I(sbi->alloc_file)->flags)) {
|
||||
clear_bit(HFSPLUS_I_ALLOC_DIRTY, &hip->flags);
|
||||
error2 = filemap_write_and_wait(sbi->alloc_file->i_mapping);
|
||||
if (!error)
|
||||
error = error2;
|
||||
@@ -709,18 +720,19 @@ int hfsplus_cat_write_inode(struct inode *inode)
|
||||
sizeof(struct hfsplus_cat_file));
|
||||
}
|
||||
|
||||
res = hfs_btree_write(tree);
|
||||
if (res) {
|
||||
pr_err("b-tree write err: %d, ino %llu\n",
|
||||
res, inode->i_ino);
|
||||
goto out;
|
||||
}
|
||||
|
||||
set_bit(HFSPLUS_I_CAT_DIRTY,
|
||||
&HFSPLUS_I(HFSPLUS_CAT_TREE_I(inode->i_sb))->flags);
|
||||
set_bit(HFSPLUS_I_CAT_DIRTY, &HFSPLUS_I(inode)->flags);
|
||||
out:
|
||||
hfs_find_exit(&fd);
|
||||
|
||||
if (!res) {
|
||||
res = hfs_btree_write(tree);
|
||||
if (res) {
|
||||
pr_err("b-tree write err: %d, ino %llu\n",
|
||||
res, inode->i_ino);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +153,10 @@ static int hfsplus_system_write_inode(struct inode *inode)
|
||||
}
|
||||
hfsplus_inode_write_fork(inode, fork);
|
||||
if (tree) {
|
||||
mutex_lock_nested(&tree->tree_lock,
|
||||
hfsplus_btree_lock_class(tree));
|
||||
int err = hfs_btree_write(tree);
|
||||
mutex_unlock(&tree->tree_lock);
|
||||
|
||||
if (err) {
|
||||
pr_err("b-tree write err: %d, ino %llu\n",
|
||||
@@ -424,12 +427,35 @@ void hfsplus_prepare_volume_header_for_commit(struct hfsplus_vh *vhdr)
|
||||
vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_INCNSTNT);
|
||||
}
|
||||
|
||||
static inline int hfsplus_get_hidden_dir_entry(struct super_block *sb,
|
||||
const struct qstr *str,
|
||||
hfsplus_cat_entry *entry)
|
||||
{
|
||||
struct hfs_find_data fd;
|
||||
int err;
|
||||
|
||||
err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd);
|
||||
if (unlikely(err))
|
||||
return err;
|
||||
|
||||
err = hfsplus_cat_build_key(sb, fd.search_key, HFSPLUS_ROOT_CNID, str);
|
||||
if (unlikely(err))
|
||||
goto free_fd;
|
||||
|
||||
err = hfsplus_brec_read_cat(&fd, entry);
|
||||
if (err)
|
||||
err = -ENOENT;
|
||||
|
||||
free_fd:
|
||||
hfs_find_exit(&fd);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
{
|
||||
struct hfsplus_vh *vhdr;
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
|
||||
hfsplus_cat_entry entry;
|
||||
struct hfs_find_data fd;
|
||||
struct inode *root, *inode;
|
||||
struct qstr str;
|
||||
struct nls_table *nls;
|
||||
@@ -565,14 +591,14 @@ static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
|
||||
str.len = sizeof(HFSP_HIDDENDIR_NAME) - 1;
|
||||
str.name = HFSP_HIDDENDIR_NAME;
|
||||
err = hfs_find_init(sbi->cat_tree, &fd);
|
||||
if (err)
|
||||
err = hfsplus_get_hidden_dir_entry(sb, &str, &entry);
|
||||
if (err == -ENOENT) {
|
||||
/*
|
||||
* Hidden directory is absent or it cannot be read.
|
||||
*/
|
||||
} else if (unlikely(err)) {
|
||||
goto out_put_root;
|
||||
err = hfsplus_cat_build_key(sb, fd.search_key, HFSPLUS_ROOT_CNID, &str);
|
||||
if (unlikely(err < 0))
|
||||
goto out_put_root;
|
||||
if (!hfs_brec_read(&fd, &entry, sizeof(entry))) {
|
||||
hfs_find_exit(&fd);
|
||||
} else {
|
||||
if (entry.type != cpu_to_be16(HFSPLUS_FOLDER)) {
|
||||
err = -EIO;
|
||||
goto out_put_root;
|
||||
@@ -583,8 +609,7 @@ static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
goto out_put_root;
|
||||
}
|
||||
sbi->hidden_dir = inode;
|
||||
} else
|
||||
hfs_find_exit(&fd);
|
||||
}
|
||||
|
||||
if (!sb_rdonly(sb)) {
|
||||
/*
|
||||
@@ -625,6 +650,8 @@ static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
}
|
||||
|
||||
mutex_unlock(&sbi->vh_mutex);
|
||||
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(sb),
|
||||
HFSPLUS_I_CAT_DIRTY);
|
||||
hfsplus_mark_inode_dirty(sbi->hidden_dir,
|
||||
HFSPLUS_I_CAT_DIRTY);
|
||||
}
|
||||
|
||||
@@ -147,9 +147,44 @@ static u16 *hfsplus_compose_lookup(u16 *p, u16 cc)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* In HFS+, a filename can contain / because : is the separator.
|
||||
* The slash is a valid filename character on macOS.
|
||||
* But on Linux, / is the path separator and
|
||||
* it cannot appear in a filename component.
|
||||
* There's a parallel mapping for the NUL character (0 -> U+2400).
|
||||
* NUL terminates strings in C/POSIX but is valid in HFS+ filenames.
|
||||
*/
|
||||
static inline
|
||||
void hfsplus_mac2linux_compatibility_check(u16 symbol, u16 *conversion,
|
||||
int name_type)
|
||||
{
|
||||
*conversion = symbol;
|
||||
|
||||
switch (name_type) {
|
||||
case HFS_XATTR_NAME:
|
||||
/* ignore conversion */
|
||||
return;
|
||||
|
||||
default:
|
||||
/* continue logic */
|
||||
break;
|
||||
}
|
||||
|
||||
switch (symbol) {
|
||||
case 0:
|
||||
*conversion = 0x2400;
|
||||
break;
|
||||
case '/':
|
||||
*conversion = ':';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int hfsplus_uni2asc(struct super_block *sb,
|
||||
const struct hfsplus_unistr *ustr,
|
||||
int max_len, char *astr, int *len_p)
|
||||
int max_len, char *astr, int *len_p,
|
||||
int name_type)
|
||||
{
|
||||
const hfsplus_unichr *ip;
|
||||
struct nls_table *nls = HFSPLUS_SB(sb)->nls;
|
||||
@@ -217,14 +252,8 @@ static int hfsplus_uni2asc(struct super_block *sb,
|
||||
hfsplus_compose_table, c1);
|
||||
if (ce1)
|
||||
break;
|
||||
switch (c0) {
|
||||
case 0:
|
||||
c0 = 0x2400;
|
||||
break;
|
||||
case '/':
|
||||
c0 = ':';
|
||||
break;
|
||||
}
|
||||
hfsplus_mac2linux_compatibility_check(c0, &c0,
|
||||
name_type);
|
||||
res = nls->uni2char(c0, op, len);
|
||||
if (res < 0) {
|
||||
if (res == -ENAMETOOLONG)
|
||||
@@ -257,16 +286,8 @@ static int hfsplus_uni2asc(struct super_block *sb,
|
||||
}
|
||||
}
|
||||
same:
|
||||
switch (c0) {
|
||||
case 0:
|
||||
cc = 0x2400;
|
||||
break;
|
||||
case '/':
|
||||
cc = ':';
|
||||
break;
|
||||
default:
|
||||
cc = c0;
|
||||
}
|
||||
hfsplus_mac2linux_compatibility_check(c0, &cc,
|
||||
name_type);
|
||||
done:
|
||||
res = nls->uni2char(cc, op, len);
|
||||
if (res < 0) {
|
||||
@@ -288,7 +309,10 @@ inline int hfsplus_uni2asc_str(struct super_block *sb,
|
||||
const struct hfsplus_unistr *ustr, char *astr,
|
||||
int *len_p)
|
||||
{
|
||||
return hfsplus_uni2asc(sb, ustr, HFSPLUS_MAX_STRLEN, astr, len_p);
|
||||
return hfsplus_uni2asc(sb,
|
||||
ustr, HFSPLUS_MAX_STRLEN,
|
||||
astr, len_p,
|
||||
HFS_REGULAR_NAME);
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(hfsplus_uni2asc_str);
|
||||
|
||||
@@ -297,22 +321,32 @@ inline int hfsplus_uni2asc_xattr_str(struct super_block *sb,
|
||||
char *astr, int *len_p)
|
||||
{
|
||||
return hfsplus_uni2asc(sb, (const struct hfsplus_unistr *)ustr,
|
||||
HFSPLUS_ATTR_MAX_STRLEN, astr, len_p);
|
||||
HFSPLUS_ATTR_MAX_STRLEN, astr, len_p,
|
||||
HFS_XATTR_NAME);
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(hfsplus_uni2asc_xattr_str);
|
||||
|
||||
/*
|
||||
* Convert one or more ASCII characters into a single unicode character.
|
||||
* Returns the number of ASCII characters corresponding to the unicode char.
|
||||
* In HFS+, a filename can contain / because : is the separator.
|
||||
* The slash is a valid filename character on macOS.
|
||||
* But on Linux, / is the path separator and
|
||||
* it cannot appear in a filename component.
|
||||
* There's a parallel mapping for the NUL character (0 -> U+2400).
|
||||
* NUL terminates strings in C/POSIX but is valid in HFS+ filenames.
|
||||
*/
|
||||
static inline int asc2unichar(struct super_block *sb, const char *astr, int len,
|
||||
wchar_t *uc)
|
||||
static inline
|
||||
void hfsplus_linux2mac_compatibility_check(wchar_t *uc, int name_type)
|
||||
{
|
||||
int size = HFSPLUS_SB(sb)->nls->char2uni(astr, len, uc);
|
||||
if (size <= 0) {
|
||||
*uc = '?';
|
||||
size = 1;
|
||||
switch (name_type) {
|
||||
case HFS_XATTR_NAME:
|
||||
/* ignore conversion */
|
||||
return;
|
||||
|
||||
default:
|
||||
/* continue logic */
|
||||
break;
|
||||
}
|
||||
|
||||
switch (*uc) {
|
||||
case 0x2400:
|
||||
*uc = 0;
|
||||
@@ -321,6 +355,23 @@ static inline int asc2unichar(struct super_block *sb, const char *astr, int len,
|
||||
*uc = '/';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 name_type)
|
||||
{
|
||||
int size = HFSPLUS_SB(sb)->nls->char2uni(astr, len, uc);
|
||||
|
||||
if (size <= 0) {
|
||||
*uc = '?';
|
||||
size = 1;
|
||||
}
|
||||
|
||||
hfsplus_linux2mac_compatibility_check(uc, name_type);
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -395,7 +446,7 @@ static u16 *decompose_unichar(wchar_t uc, int *size, u16 *hangul_buffer)
|
||||
|
||||
int hfsplus_asc2uni(struct super_block *sb,
|
||||
struct hfsplus_unistr *ustr, int max_unistr_len,
|
||||
const char *astr, int len)
|
||||
const char *astr, int len, int name_type)
|
||||
{
|
||||
int size, dsize, decompose;
|
||||
u16 *dstr, outlen = 0;
|
||||
@@ -404,7 +455,7 @@ int hfsplus_asc2uni(struct super_block *sb,
|
||||
|
||||
decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
||||
while (outlen < max_unistr_len && len > 0) {
|
||||
size = asc2unichar(sb, astr, len, &c);
|
||||
size = asc2unichar(sb, astr, len, &c, name_type);
|
||||
|
||||
if (decompose)
|
||||
dstr = decompose_unichar(c, &dsize, dhangul);
|
||||
@@ -452,7 +503,7 @@ int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str)
|
||||
len = str->len;
|
||||
while (len > 0) {
|
||||
int dsize;
|
||||
size = asc2unichar(sb, astr, len, &c);
|
||||
size = asc2unichar(sb, astr, len, &c, HFS_REGULAR_NAME);
|
||||
astr += size;
|
||||
len -= size;
|
||||
|
||||
@@ -510,7 +561,8 @@ int hfsplus_compare_dentry(const struct dentry *dentry,
|
||||
|
||||
while (len1 > 0 && len2 > 0) {
|
||||
if (!dsize1) {
|
||||
size = asc2unichar(sb, astr1, len1, &c);
|
||||
size = asc2unichar(sb, astr1, len1, &c,
|
||||
HFS_REGULAR_NAME);
|
||||
astr1 += size;
|
||||
len1 -= size;
|
||||
|
||||
@@ -525,7 +577,8 @@ int hfsplus_compare_dentry(const struct dentry *dentry,
|
||||
}
|
||||
|
||||
if (!dsize2) {
|
||||
size = asc2unichar(sb, astr2, len2, &c);
|
||||
size = asc2unichar(sb, astr2, len2, &c,
|
||||
HFS_REGULAR_NAME);
|
||||
astr2 += size;
|
||||
len2 -= size;
|
||||
|
||||
|
||||
@@ -715,28 +715,32 @@ static void hfsplus_asc2uni_basic_test(struct kunit *test)
|
||||
|
||||
/* Test simple ASCII string conversion */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
|
||||
HFSPLUS_MAX_STRLEN, "hello", 5);
|
||||
HFSPLUS_MAX_STRLEN, "hello", 5,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
check_unistr_content(test, &mock_env->str1, "hello");
|
||||
|
||||
/* Test empty string */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
|
||||
HFSPLUS_MAX_STRLEN, "", 0);
|
||||
HFSPLUS_MAX_STRLEN, "", 0,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length));
|
||||
|
||||
/* Test single character */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
|
||||
HFSPLUS_MAX_STRLEN, "A", 1);
|
||||
HFSPLUS_MAX_STRLEN, "A", 1,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
check_unistr_content(test, &mock_env->str1, "A");
|
||||
|
||||
/* Test null-terminated string with explicit length */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
|
||||
HFSPLUS_MAX_STRLEN, "test\0extra", 4);
|
||||
HFSPLUS_MAX_STRLEN, "test\0extra", 4,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
check_unistr_content(test, &mock_env->str1, "test");
|
||||
@@ -762,7 +766,8 @@ static void hfsplus_asc2uni_special_chars_test(struct kunit *test)
|
||||
|
||||
/* Test colon conversion (should become forward slash) */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
|
||||
HFSPLUS_MAX_STRLEN, ":", 1);
|
||||
HFSPLUS_MAX_STRLEN, ":", 1,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, 1, be16_to_cpu(mock_env->str1.length));
|
||||
@@ -770,7 +775,8 @@ static void hfsplus_asc2uni_special_chars_test(struct kunit *test)
|
||||
|
||||
/* Test string with mixed special characters */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
|
||||
HFSPLUS_MAX_STRLEN, "a:b", 3);
|
||||
HFSPLUS_MAX_STRLEN, "a:b", 3,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(mock_env->str1.length));
|
||||
@@ -780,7 +786,8 @@ static void hfsplus_asc2uni_special_chars_test(struct kunit *test)
|
||||
|
||||
/* Test multiple special characters */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
|
||||
HFSPLUS_MAX_STRLEN, ":::", 3);
|
||||
HFSPLUS_MAX_STRLEN, ":::", 3,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(mock_env->str1.length));
|
||||
@@ -811,7 +818,8 @@ static void hfsplus_asc2uni_buffer_limits_test(struct kunit *test)
|
||||
memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN);
|
||||
result = hfsplus_asc2uni(&mock_sb->sb,
|
||||
&mock_env->str1, HFSPLUS_MAX_STRLEN,
|
||||
mock_env->buf, HFSPLUS_MAX_STRLEN);
|
||||
mock_env->buf, HFSPLUS_MAX_STRLEN,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN,
|
||||
@@ -821,7 +829,8 @@ static void hfsplus_asc2uni_buffer_limits_test(struct kunit *test)
|
||||
memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN + 5);
|
||||
result = hfsplus_asc2uni(&mock_sb->sb,
|
||||
&mock_env->str1, HFSPLUS_MAX_STRLEN,
|
||||
mock_env->buf, HFSPLUS_MAX_STRLEN + 5);
|
||||
mock_env->buf, HFSPLUS_MAX_STRLEN + 5,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
|
||||
KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN,
|
||||
@@ -829,13 +838,15 @@ static void hfsplus_asc2uni_buffer_limits_test(struct kunit *test)
|
||||
|
||||
/* Test with smaller max_unistr_len */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb,
|
||||
&mock_env->str1, 5, "toolongstring", 13);
|
||||
&mock_env->str1, 5, "toolongstring", 13,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
|
||||
KUNIT_EXPECT_EQ(test, 5, be16_to_cpu(mock_env->str1.length));
|
||||
|
||||
/* Test zero max length */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 0, "test", 4);
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 0, "test", 4,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
|
||||
KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length));
|
||||
@@ -859,28 +870,32 @@ static void hfsplus_asc2uni_edge_cases_test(struct kunit *test)
|
||||
|
||||
/* Test zero length input */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb,
|
||||
&ustr, HFSPLUS_MAX_STRLEN, "test", 0);
|
||||
&ustr, HFSPLUS_MAX_STRLEN, "test", 0,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(ustr.length));
|
||||
|
||||
/* Test input with length mismatch */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb,
|
||||
&ustr, HFSPLUS_MAX_STRLEN, "hello", 3);
|
||||
&ustr, HFSPLUS_MAX_STRLEN, "hello", 3,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
check_unistr_content(test, &ustr, "hel");
|
||||
|
||||
/* Test with various printable ASCII characters */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb,
|
||||
&ustr, HFSPLUS_MAX_STRLEN, "ABC123!@#", 9);
|
||||
&ustr, HFSPLUS_MAX_STRLEN, "ABC123!@#", 9,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
check_unistr_content(test, &ustr, "ABC123!@#");
|
||||
|
||||
/* Test null character in the middle */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb,
|
||||
&ustr, HFSPLUS_MAX_STRLEN, test_str, 3);
|
||||
&ustr, HFSPLUS_MAX_STRLEN, test_str, 3,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
|
||||
@@ -909,7 +924,8 @@ static void hfsplus_asc2uni_decompose_test(struct kunit *test)
|
||||
/* Test with decomposition disabled (default) */
|
||||
clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
|
||||
HFSPLUS_MAX_STRLEN, "test", 4);
|
||||
HFSPLUS_MAX_STRLEN, "test", 4,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
check_unistr_content(test, &mock_env->str1, "test");
|
||||
@@ -917,7 +933,8 @@ static void hfsplus_asc2uni_decompose_test(struct kunit *test)
|
||||
/* Test with decomposition enabled */
|
||||
set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str2,
|
||||
HFSPLUS_MAX_STRLEN, "test", 4);
|
||||
HFSPLUS_MAX_STRLEN, "test", 4,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
check_unistr_content(test, &mock_env->str2, "test");
|
||||
|
||||
@@ -50,7 +50,7 @@ static bool is_known_namespace(const char *name)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void hfsplus_init_header_node(struct inode *attr_file,
|
||||
static u32 hfsplus_init_header_node(struct inode *attr_file,
|
||||
u32 clump_size,
|
||||
char *buf, u16 node_size)
|
||||
{
|
||||
@@ -59,6 +59,7 @@ static void hfsplus_init_header_node(struct inode *attr_file,
|
||||
u16 offset;
|
||||
__be16 *rec_offsets;
|
||||
u32 hdr_node_map_rec_bits;
|
||||
u32 map_nodes = 0;
|
||||
char *bmp;
|
||||
u32 used_nodes;
|
||||
u32 used_bmp_bytes;
|
||||
@@ -93,7 +94,6 @@ static void hfsplus_init_header_node(struct inode *attr_file,
|
||||
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) -
|
||||
@@ -116,21 +116,100 @@ static void hfsplus_init_header_node(struct inode *attr_file,
|
||||
*bmp = ~(0xFF >> used_nodes);
|
||||
offset += hdr_node_map_rec_bits / 8;
|
||||
*--rec_offsets = cpu_to_be16(offset);
|
||||
|
||||
return map_nodes;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a map node buffer. Map nodes have a single bitmap record.
|
||||
*/
|
||||
static void hfsplus_init_map_node(u8 *buf, u16 node_size, u32 next_node)
|
||||
{
|
||||
struct hfs_bnode_desc *desc;
|
||||
__be16 *rec_offsets;
|
||||
size_t rec_size = sizeof(__be16);
|
||||
u16 offset;
|
||||
|
||||
memset(buf, 0, node_size);
|
||||
|
||||
desc = (struct hfs_bnode_desc *)buf;
|
||||
desc->type = HFS_NODE_MAP;
|
||||
desc->num_recs = cpu_to_be16(1);
|
||||
desc->next = cpu_to_be32(next_node);
|
||||
|
||||
/*
|
||||
* A map node consists of the node descriptor and a single
|
||||
* map record. The map record is a continuation of the map
|
||||
* record contained in the header node. The size of the map
|
||||
* record is the size of the node, minus the size of
|
||||
* the node descriptor (14 bytes), minus the size of two offsets
|
||||
* (4 bytes), minus two bytes of free space. That is, the size of
|
||||
* the map record is the size of the node minus 20 bytes;
|
||||
* this keeps the length of the map record an even multiple of 4 bytes.
|
||||
* The start of the map record is not aligned to a 4-byte boundary:
|
||||
* it starts immediately after the node descriptor
|
||||
* (at an offset of 14 bytes).
|
||||
*
|
||||
* Two record offsets stored at the end of the node:
|
||||
* record[1] = start of record 0 -> sizeof(hfs_bnode_desc)
|
||||
* record[2] = start of free space
|
||||
*/
|
||||
rec_offsets = (__be16 *)(buf + node_size);
|
||||
|
||||
/* record #1 */
|
||||
offset = sizeof(struct hfs_bnode_desc);
|
||||
*--rec_offsets = cpu_to_be16(offset);
|
||||
|
||||
/* record #2 */
|
||||
offset = node_size;
|
||||
offset -= (u16)HFSPLUS_BTREE_MAP_NODE_RECS_COUNT * rec_size;
|
||||
offset -= HFSPLUS_BTREE_MAP_NODE_RESERVED_BYTES;
|
||||
*--rec_offsets = cpu_to_be16(offset);
|
||||
}
|
||||
|
||||
static inline
|
||||
int hfsplus_write_attributes_file_node(struct inode *attr_file, char *buf,
|
||||
u16 node_size, int *index)
|
||||
{
|
||||
struct address_space *mapping;
|
||||
struct page *page;
|
||||
void *kaddr;
|
||||
u32 written = 0;
|
||||
|
||||
mapping = attr_file->i_mapping;
|
||||
|
||||
for (; written < node_size; (*index)++, written += PAGE_SIZE) {
|
||||
page = read_mapping_page(mapping, *index, NULL);
|
||||
if (IS_ERR(page))
|
||||
return PTR_ERR(page);
|
||||
|
||||
kaddr = kmap_local_page(page);
|
||||
memcpy(kaddr, buf + written,
|
||||
min_t(size_t, PAGE_SIZE, node_size - written));
|
||||
kunmap_local(kaddr);
|
||||
|
||||
set_page_dirty(page);
|
||||
put_page(page);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
struct hfs_bnode_desc *desc;
|
||||
u32 clump_size;
|
||||
u16 node_size = HFSPLUS_ATTR_TREE_NODE_SIZE;
|
||||
u32 next_node;
|
||||
u32 map_node_idx;
|
||||
u32 map_nodes;
|
||||
char *buf;
|
||||
int index, written;
|
||||
struct address_space *mapping;
|
||||
struct page *page;
|
||||
int index;
|
||||
int old_state = HFSPLUS_EMPTY_ATTR_TREE;
|
||||
int err = 0;
|
||||
|
||||
hfs_dbg("ino %d\n", HFSPLUS_ATTR_CNID);
|
||||
|
||||
@@ -195,7 +274,7 @@ check_attr_tree_state_again:
|
||||
}
|
||||
|
||||
while (hip->alloc_blocks < hip->clump_blocks) {
|
||||
err = hfsplus_file_extend(attr_file, false);
|
||||
err = hfsplus_file_extend(attr_file, true);
|
||||
if (unlikely(err)) {
|
||||
pr_err("failed to extend attributes file\n");
|
||||
goto end_attr_file_creation;
|
||||
@@ -212,30 +291,33 @@ check_attr_tree_state_again:
|
||||
goto end_attr_file_creation;
|
||||
}
|
||||
|
||||
hfsplus_init_header_node(attr_file, clump_size, buf, node_size);
|
||||
map_nodes = hfsplus_init_header_node(attr_file, clump_size, buf, node_size);
|
||||
|
||||
mapping = attr_file->i_mapping;
|
||||
desc = (struct hfs_bnode_desc *)buf;
|
||||
next_node = be32_to_cpu(desc->next);
|
||||
|
||||
index = 0;
|
||||
written = 0;
|
||||
for (; written < node_size; index++, written += PAGE_SIZE) {
|
||||
void *kaddr;
|
||||
|
||||
page = read_mapping_page(mapping, index, NULL);
|
||||
if (IS_ERR(page)) {
|
||||
err = PTR_ERR(page);
|
||||
err = hfsplus_write_attributes_file_node(attr_file, buf,
|
||||
node_size, &index);
|
||||
if (unlikely(err))
|
||||
goto failed_header_node_init;
|
||||
|
||||
for (map_node_idx = 0; map_node_idx < map_nodes; map_node_idx++) {
|
||||
if (next_node >= map_nodes)
|
||||
next_node = 0;
|
||||
|
||||
hfsplus_init_map_node(buf, node_size, next_node);
|
||||
|
||||
err = hfsplus_write_attributes_file_node(attr_file, buf,
|
||||
node_size, &index);
|
||||
if (unlikely(err))
|
||||
goto failed_header_node_init;
|
||||
}
|
||||
|
||||
kaddr = kmap_atomic(page);
|
||||
memcpy(kaddr, buf + written,
|
||||
min_t(size_t, PAGE_SIZE, node_size - written));
|
||||
kunmap_atomic(kaddr);
|
||||
|
||||
set_page_dirty(page);
|
||||
put_page(page);
|
||||
next_node++;
|
||||
}
|
||||
|
||||
hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(sb), HFSPLUS_I_ATTR_DIRTY);
|
||||
hfsplus_mark_inode_dirty(attr_file, HFSPLUS_I_ATTR_DIRTY);
|
||||
|
||||
sbi->attr_tree = hfs_btree_open(sb, HFSPLUS_ATTR_CNID);
|
||||
@@ -314,8 +396,11 @@ int __hfsplus_setxattr(struct inode *inode, const char *name,
|
||||
hfs_bnode_write(cat_fd.bnode, &entry,
|
||||
cat_fd.entryoffset,
|
||||
sizeof(struct hfsplus_cat_folder));
|
||||
hfsplus_mark_inode_dirty(inode,
|
||||
hfsplus_mark_inode_dirty(
|
||||
HFSPLUS_CAT_TREE_I(inode->i_sb),
|
||||
HFSPLUS_I_CAT_DIRTY);
|
||||
hfsplus_mark_inode_dirty(inode,
|
||||
HFSPLUS_I_CAT_DIRTY);
|
||||
} else {
|
||||
err = -ERANGE;
|
||||
goto end_setxattr;
|
||||
@@ -327,8 +412,11 @@ int __hfsplus_setxattr(struct inode *inode, const char *name,
|
||||
hfs_bnode_write(cat_fd.bnode, &entry,
|
||||
cat_fd.entryoffset,
|
||||
sizeof(struct hfsplus_cat_file));
|
||||
hfsplus_mark_inode_dirty(inode,
|
||||
hfsplus_mark_inode_dirty(
|
||||
HFSPLUS_CAT_TREE_I(inode->i_sb),
|
||||
HFSPLUS_I_CAT_DIRTY);
|
||||
hfsplus_mark_inode_dirty(inode,
|
||||
HFSPLUS_I_CAT_DIRTY);
|
||||
} else {
|
||||
err = -ERANGE;
|
||||
goto end_setxattr;
|
||||
@@ -381,6 +469,8 @@ int __hfsplus_setxattr(struct inode *inode, const char *name,
|
||||
hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset +
|
||||
offsetof(struct hfsplus_cat_folder, flags),
|
||||
cat_entry_flags);
|
||||
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(inode->i_sb),
|
||||
HFSPLUS_I_CAT_DIRTY);
|
||||
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,
|
||||
@@ -392,6 +482,8 @@ int __hfsplus_setxattr(struct inode *inode, const char *name,
|
||||
hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset +
|
||||
offsetof(struct hfsplus_cat_file, flags),
|
||||
cat_entry_flags);
|
||||
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(inode->i_sb),
|
||||
HFSPLUS_I_CAT_DIRTY);
|
||||
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY);
|
||||
} else {
|
||||
pr_err("invalid catalog entry type\n");
|
||||
@@ -399,6 +491,8 @@ int __hfsplus_setxattr(struct inode *inode, const char *name,
|
||||
goto end_setxattr;
|
||||
}
|
||||
|
||||
inode_set_ctime_current(inode);
|
||||
|
||||
end_setxattr:
|
||||
hfs_find_exit(&cat_fd);
|
||||
hfs_dbg("finished: res %d\n", err);
|
||||
@@ -549,10 +643,10 @@ ssize_t __hfsplus_getxattr(struct inode *inode, const char *name,
|
||||
|
||||
res = hfsplus_find_attr(inode->i_sb, inode->i_ino, name, &fd);
|
||||
if (res) {
|
||||
if (res == -ENOENT)
|
||||
if (res == -ENOENT || res == -ENODATA)
|
||||
res = -ENODATA;
|
||||
else
|
||||
pr_err("xattr searching failed\n");
|
||||
pr_err("xattr search failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -744,7 +838,7 @@ ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size)
|
||||
|
||||
err = hfsplus_find_attr(inode->i_sb, inode->i_ino, NULL, &fd);
|
||||
if (err) {
|
||||
if (err == -ENOENT) {
|
||||
if (err == -ENOENT || err == -ENODATA) {
|
||||
res = 0;
|
||||
goto end_listxattr;
|
||||
} else {
|
||||
@@ -862,6 +956,8 @@ static int hfsplus_removexattr(struct inode *inode, const char *name)
|
||||
hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset +
|
||||
offsetof(struct hfsplus_cat_folder, flags),
|
||||
flags);
|
||||
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(inode->i_sb),
|
||||
HFSPLUS_I_CAT_DIRTY);
|
||||
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 +
|
||||
@@ -873,6 +969,8 @@ static int hfsplus_removexattr(struct inode *inode, const char *name)
|
||||
hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset +
|
||||
offsetof(struct hfsplus_cat_file, flags),
|
||||
flags);
|
||||
hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(inode->i_sb),
|
||||
HFSPLUS_I_CAT_DIRTY);
|
||||
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY);
|
||||
} else {
|
||||
pr_err("invalid catalog entry type\n");
|
||||
@@ -880,6 +978,8 @@ static int hfsplus_removexattr(struct inode *inode, const char *name)
|
||||
goto end_removexattr;
|
||||
}
|
||||
|
||||
inode_set_ctime_current(inode);
|
||||
|
||||
end_removexattr:
|
||||
hfs_find_exit(&cat_fd);
|
||||
|
||||
|
||||
@@ -166,6 +166,11 @@ struct hfsplus_attr_unistr {
|
||||
hfsplus_unichr unicode[HFSPLUS_ATTR_MAX_STRLEN];
|
||||
} __packed;
|
||||
|
||||
enum {
|
||||
HFS_REGULAR_NAME,
|
||||
HFS_XATTR_NAME,
|
||||
};
|
||||
|
||||
struct hfs_extent {
|
||||
__be16 block;
|
||||
__be16 count;
|
||||
@@ -510,7 +515,11 @@ struct hfs_btree_header_rec {
|
||||
#define HFSPLUS_NODE_MXSZ 32768
|
||||
#define HFSPLUS_ATTR_TREE_NODE_SIZE 8192
|
||||
#define HFSPLUS_BTREE_HDR_NODE_RECS_COUNT 3
|
||||
#define HFSPLUS_BTREE_HDR_MAP_REC_INDEX 2 /* Map (bitmap) record in Header node */
|
||||
#define HFSPLUS_BTREE_MAP_NODE_REC_INDEX 0 /* Map record in Map Node */
|
||||
#define HFSPLUS_BTREE_HDR_USER_BYTES 128
|
||||
#define HFSPLUS_BTREE_MAP_NODE_RECS_COUNT 2
|
||||
#define HFSPLUS_BTREE_MAP_NODE_RESERVED_BYTES 2
|
||||
|
||||
/* btree key type */
|
||||
#define HFSPLUS_KEY_CASEFOLDING 0xCF /* case-insensitive */
|
||||
|
||||
Reference in New Issue
Block a user