diff --git a/fs/hfsplus/attributes.c b/fs/hfsplus/attributes.c index 174cd13106ad..7c2e589d4553 100644 --- a/fs/hfsplus/attributes.c +++ b/fs/hfsplus/attributes.c @@ -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; } diff --git a/fs/hfsplus/bfind.c b/fs/hfsplus/bfind.c index 336d654861c5..9a55fa6d5294 100644 --- a/fs/hfsplus/bfind.c +++ b/fs/hfsplus/bfind.c @@ -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; +} diff --git a/fs/hfsplus/bnode.c b/fs/hfsplus/bnode.c index 250a226336ea..f8b5a8ae58ff 100644 --- a/fs/hfsplus/bnode.c +++ b/fs/hfsplus/bnode.c @@ -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) diff --git a/fs/hfsplus/brec.c b/fs/hfsplus/brec.c index 6796c1a80e99..e3df89284079 100644 --- a/fs/hfsplus/brec.c +++ b/fs/hfsplus/brec.c @@ -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); diff --git a/fs/hfsplus/btree.c b/fs/hfsplus/btree.c index 1220a2f22737..761c74ccd653 100644 --- a/fs/hfsplus/btree.c +++ b/fs/hfsplus/btree.c @@ -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); } diff --git a/fs/hfsplus/catalog.c b/fs/hfsplus/catalog.c index 0e961e99b985..9bc8a6c48fd3 100644 --- a/fs/hfsplus/catalog.c +++ b/fs/hfsplus/catalog.c @@ -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: diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index 054f6da46033..47194370c2c5 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -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; } diff --git a/fs/hfsplus/extents.c b/fs/hfsplus/extents.c index 474fde1a1653..813e68b8ecd6 100644 --- a/fs/hfsplus/extents.c +++ b/fs/hfsplus/extents.c @@ -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); } diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h index 5f891b73a646..3545b8dbf11c 100644 --- a/fs/hfsplus/hfsplus_fs.h +++ b/fs/hfsplus/hfsplus_fs.h @@ -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: " diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index 02be32dc6833..d05891ec492e 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -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; } diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c index b3917249c206..40a0feda716b 100644 --- a/fs/hfsplus/super.c +++ b/fs/hfsplus/super.c @@ -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); } diff --git a/fs/hfsplus/unicode.c b/fs/hfsplus/unicode.c index d3a142f4518b..008fec186382 100644 --- a/fs/hfsplus/unicode.c +++ b/fs/hfsplus/unicode.c @@ -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; diff --git a/fs/hfsplus/unicode_test.c b/fs/hfsplus/unicode_test.c index 26145bf88946..83737c9bafa0 100644 --- a/fs/hfsplus/unicode_test.c +++ b/fs/hfsplus/unicode_test.c @@ -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"); diff --git a/fs/hfsplus/xattr.c b/fs/hfsplus/xattr.c index c70bb6f494b2..452a1f9becb2 100644 --- a/fs/hfsplus/xattr.c +++ b/fs/hfsplus/xattr.c @@ -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); diff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h index dadb5e0aa8a3..07dfc39630ab 100644 --- a/include/linux/hfs_common.h +++ b/include/linux/hfs_common.h @@ -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 */