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:
Linus Torvalds
2026-04-13 16:50:38 -07:00
15 changed files with 682 additions and 182 deletions

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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:

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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: "

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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");

View File

@@ -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);

View File

@@ -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 */