mirror of
https://github.com/torvalds/linux.git
synced 2026-10-23 15:32:04 +02:00
hfs/hfsplus updates for v7.4
- hfs: handle extent B-tree write errors
- hfsplus: add KUnit coverage for canonical reordering and legacy fixups
- hfsplus: canonicalize decomposed catalog names like fsck_hfs expects
- hfsplus: add Unicode combining-class and legacy decomposition data
- hfs/hfsplus: serialize B-tree close against folio release
- hfs: return -EPERM for unsupported setattr changes
-----BEGIN PGP SIGNATURE-----
iHUEABYKAB0WIQT4wVoLCG92poNnMFAhI4xTh21NnQUCatKcPQAKCRAhI4xTh21N
neWQAP0eW63MZp/7GJL/yLOHfxQYGGCf90rqOhYqzlbvXiK5VgEAznpnjbP1hy3x
SIAEJ/1kn8uPaQWYqOPwgkNrBtqgPAk=
=ovZs
-----END PGP SIGNATURE-----
Merge tag 'hfs-v7.4-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/vdubeyko/hfs
Pull hfs/hfsplus updates from Viacheslav Dubeyko:
"Fix {hfs,hfsplus}_btree_close() (Bruno Produit):
- B-tree nodes with a zero reference count remain in the node hash
until folio reclaim or tree teardown frees them. The folio release
callbacks remove nodes while holding hash_lock, but
hfs_btree_close() walks and frees the same hash without that lock.
Reclaim can therefore unhash and free a node after close has loaded
its pointer, causing a use-after-free or double-free. Detach each
node with hfs_bnode_unhash() while holding hash_lock before
inspecting and freeing it. Drop the lock before hfs_bnode_free() so
a large tree is not freed while holding a spinlock.
Fix hfs_inode_setattr() and hfs_brec_insert() (Davy Felipe):
- hfs_inode_setattr() rejects unsupported UID, GID and mode changes,
but returns the value stored in error when the filesystem is not
mounted with the quiet option. At this point error is zero, as
execution has already passed a successful setattr_prepare() call.
As a result, unsupported attribute changes are incorrectly reported
to userspace as successful even though the inode remains unchanged.
Return -EPERM for these operations while preserving the existing
behavior for quiet mounts.
- hfs_brec_insert() may fail while inserting a new extent record, but
__hfs_ext_write_extent() currently ignores its return value and
clears HFS_FLG_EXT_DIRTY and HFS_FLG_EXT_NEW as if the insertion
had succeeded. Use a reusable B-tree node range helper that takes
the find data and the expected record size. The helper validates
the bnode and tree pointers, entry offset and entry length, and
ensures that the write range fits within the node.
Fix failure in xfstests generic/339 (Viacheslav Dubeyko):
- The test left an HFS+ volume corrupted and, as a result,
fsck.hfsplus reported a long run of "Illegal name". The root cause
is that fs/hfsplus/unicode.c's decomposition table (Apple Technote
#1150) decomposes each source character entirely on its own and
never does anything further with the result. Real fsck_hfs has
carried a permanent "FixDecomps" fixup in CatalogCheck.c for
exactly what that leaves unhandled. The fix makes hfsplus store,
hash and compare names the same way current macOS does"
* tag 'hfs-v7.4-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/vdubeyko/hfs:
hfs: handle extent B-tree write errors
hfsplus: add KUnit coverage for canonical reordering and legacy fixups
hfsplus: canonicalize decomposed catalog names like fsck_hfs expects
hfsplus: add Unicode combining-class and legacy decomposition data
hfs/hfsplus: serialize B-tree close against folio release
hfs: return -EPERM for unsupported setattr changes
This commit is contained in:
commit
dd7b15aeac
|
|
@ -310,15 +310,18 @@ void hfs_btree_close(struct hfs_btree *tree)
|
|||
return;
|
||||
|
||||
for (i = 0; i < NODE_HASH_SIZE; i++) {
|
||||
spin_lock(&tree->hash_lock);
|
||||
while ((node = tree->node_hash[i])) {
|
||||
tree->node_hash[i] = node->next_hash;
|
||||
hfs_bnode_unhash(node);
|
||||
spin_unlock(&tree->hash_lock);
|
||||
if (atomic_read(&node->refcnt))
|
||||
pr_err("node %d:%d still has %d user(s)!\n",
|
||||
node->tree->cnid, node->this,
|
||||
atomic_read(&node->refcnt));
|
||||
hfs_bnode_free(node);
|
||||
tree->node_hash_cnt--;
|
||||
spin_lock(&tree->hash_lock);
|
||||
}
|
||||
spin_unlock(&tree->hash_lock);
|
||||
}
|
||||
iput(tree->inode);
|
||||
kfree(tree);
|
||||
|
|
|
|||
|
|
@ -84,6 +84,25 @@ struct hfs_find_data {
|
|||
int entryoffset, entrylength;
|
||||
};
|
||||
|
||||
static inline bool is_hfs_bnode_range_valid(struct hfs_find_data *fd,
|
||||
size_t expected_len)
|
||||
{
|
||||
struct hfs_bnode *node;
|
||||
|
||||
if (!fd)
|
||||
return false;
|
||||
|
||||
node = fd->bnode;
|
||||
if (!node || !node->tree)
|
||||
return false;
|
||||
|
||||
if (fd->entryoffset < 0 || fd->entrylength != expected_len)
|
||||
return false;
|
||||
|
||||
return (u64)fd->entryoffset + fd->entrylength <=
|
||||
node->tree->node_size;
|
||||
}
|
||||
|
||||
|
||||
/* btree.c */
|
||||
extern struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id,
|
||||
|
|
|
|||
|
|
@ -121,12 +121,18 @@ static int __hfs_ext_write_extent(struct inode *inode, struct hfs_find_data *fd)
|
|||
res = hfs_bmap_reserve(fd->tree, fd->tree->depth + 1);
|
||||
if (res)
|
||||
return res;
|
||||
hfs_brec_insert(fd, HFS_I(inode)->cached_extents, sizeof(hfs_extent_rec));
|
||||
res = hfs_brec_insert(fd, HFS_I(inode)->cached_extents,
|
||||
sizeof(hfs_extent_rec));
|
||||
if (res)
|
||||
return res;
|
||||
HFS_I(inode)->flags &= ~(HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW);
|
||||
} else {
|
||||
if (res)
|
||||
return res;
|
||||
hfs_bnode_write(fd->bnode, HFS_I(inode)->cached_extents, fd->entryoffset, fd->entrylength);
|
||||
if (!is_hfs_bnode_range_valid(fd, sizeof(hfs_extent_rec)))
|
||||
return -ERANGE;
|
||||
hfs_bnode_write(fd->bnode, HFS_I(inode)->cached_extents,
|
||||
fd->entryoffset, fd->entrylength);
|
||||
HFS_I(inode)->flags &= ~HFS_FLG_EXT_DIRTY;
|
||||
}
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -663,7 +663,7 @@ int hfs_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
|
|||
((S_ISDIR(inode->i_mode) &&
|
||||
(attr->ia_mode != inode->i_mode)) ||
|
||||
(attr->ia_mode & ~HFS_VALID_MODE_BITS)))) {
|
||||
return hsb->s_quiet ? 0 : error;
|
||||
return hsb->s_quiet ? 0 : -EPERM;
|
||||
}
|
||||
|
||||
/* map file permissions to the closest allowable permissions in HFS */
|
||||
|
|
|
|||
|
|
@ -417,16 +417,19 @@ void hfs_btree_close(struct hfs_btree *tree)
|
|||
return;
|
||||
|
||||
for (i = 0; i < NODE_HASH_SIZE; i++) {
|
||||
spin_lock(&tree->hash_lock);
|
||||
while ((node = tree->node_hash[i])) {
|
||||
tree->node_hash[i] = node->next_hash;
|
||||
hfs_bnode_unhash(node);
|
||||
spin_unlock(&tree->hash_lock);
|
||||
if (atomic_read(&node->refcnt))
|
||||
pr_crit("node %d:%d "
|
||||
"still has %d user(s)!\n",
|
||||
node->tree->cnid, node->this,
|
||||
atomic_read(&node->refcnt));
|
||||
hfs_bnode_free(node);
|
||||
tree->node_hash_cnt--;
|
||||
spin_lock(&tree->hash_lock);
|
||||
}
|
||||
spin_unlock(&tree->hash_lock);
|
||||
}
|
||||
iput(tree->inode);
|
||||
kfree(tree);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <linux/blkdev.h>
|
||||
#include <linux/fs_context.h>
|
||||
#include "hfsplus_raw.h"
|
||||
#include "unicode.h"
|
||||
|
||||
/* Runtime config options */
|
||||
#define HFSPLUS_DEF_CR_TYPE 0x3F3F3F3F /* '????' */
|
||||
|
|
@ -486,11 +487,6 @@ void hfsplus_mark_mdb_dirty(struct super_block *sb);
|
|||
void hfsplus_prepare_volume_header_for_commit(struct hfsplus_vh *vhdr);
|
||||
int hfsplus_commit_superblock(struct super_block *sb);
|
||||
|
||||
/* tables.c */
|
||||
extern u16 hfsplus_case_fold_table[];
|
||||
extern u16 hfsplus_decompose_table[];
|
||||
extern u16 hfsplus_compose_table[];
|
||||
|
||||
/* unicode.c */
|
||||
int hfsplus_strcasecmp(const struct hfsplus_unistr *s1,
|
||||
const struct hfsplus_unistr *s2);
|
||||
|
|
|
|||
|
|
@ -3244,3 +3244,499 @@ u16 hfsplus_compose_table[] = {
|
|||
/* 0x0342 0x0314 0x03c9 0x0345 */
|
||||
0x1fa7, 0x0000,
|
||||
};
|
||||
|
||||
/*
|
||||
* Unicode canonical combining class (ccc) table, restricted to the Basic
|
||||
* Multilingual Plane: HFS+ names are stored as UTF-16 and this kernel's
|
||||
* wchar_t (see MAX_WCHAR_T in include/linux/nls.h) tops out at U+FFFF, so
|
||||
* no HFS+ catalog name can contain a code point above the BMP anyway.
|
||||
*
|
||||
* Generated from the Unicode Character Database, version 12.1.0
|
||||
* (https://www.unicode.org/Public/12.1.0/ucd/extracted/DerivedCombiningClass.txt),
|
||||
* the same UCD version fs/unicode/README.utf8data documents. Class 0
|
||||
* ("Not_Reordered") is the default for every code point not listed here
|
||||
* and needs no entry.
|
||||
*
|
||||
* Used by hfsplus_canonical_reorder() to apply the Unicode Canonical
|
||||
* Ordering Algorithm to a decomposed name, matching what macOS's own
|
||||
* fsck_hfs (FixDecomps()) requires of a stored catalog name.
|
||||
*/
|
||||
struct hfsplus_ccc_range hfsplus_ccc_table[] = {
|
||||
{ 0x0300, 0x0314, 230 },
|
||||
{ 0x0315, 0x0315, 232 },
|
||||
{ 0x0316, 0x0319, 220 },
|
||||
{ 0x031a, 0x031a, 232 },
|
||||
{ 0x031b, 0x031b, 216 },
|
||||
{ 0x031c, 0x0320, 220 },
|
||||
{ 0x0321, 0x0322, 202 },
|
||||
{ 0x0323, 0x0326, 220 },
|
||||
{ 0x0327, 0x0328, 202 },
|
||||
{ 0x0329, 0x0333, 220 },
|
||||
{ 0x0334, 0x0338, 1 },
|
||||
{ 0x0339, 0x033c, 220 },
|
||||
{ 0x033d, 0x0344, 230 },
|
||||
{ 0x0345, 0x0345, 240 },
|
||||
{ 0x0346, 0x0346, 230 },
|
||||
{ 0x0347, 0x0349, 220 },
|
||||
{ 0x034a, 0x034c, 230 },
|
||||
{ 0x034d, 0x034e, 220 },
|
||||
{ 0x0350, 0x0352, 230 },
|
||||
{ 0x0353, 0x0356, 220 },
|
||||
{ 0x0357, 0x0357, 230 },
|
||||
{ 0x0358, 0x0358, 232 },
|
||||
{ 0x0359, 0x035a, 220 },
|
||||
{ 0x035b, 0x035b, 230 },
|
||||
{ 0x035c, 0x035c, 233 },
|
||||
{ 0x035d, 0x035e, 234 },
|
||||
{ 0x035f, 0x035f, 233 },
|
||||
{ 0x0360, 0x0361, 234 },
|
||||
{ 0x0362, 0x0362, 233 },
|
||||
{ 0x0363, 0x036f, 230 },
|
||||
{ 0x0483, 0x0487, 230 },
|
||||
{ 0x0591, 0x0591, 220 },
|
||||
{ 0x0592, 0x0595, 230 },
|
||||
{ 0x0596, 0x0596, 220 },
|
||||
{ 0x0597, 0x0599, 230 },
|
||||
{ 0x059a, 0x059a, 222 },
|
||||
{ 0x059b, 0x059b, 220 },
|
||||
{ 0x059c, 0x05a1, 230 },
|
||||
{ 0x05a2, 0x05a7, 220 },
|
||||
{ 0x05a8, 0x05a9, 230 },
|
||||
{ 0x05aa, 0x05aa, 220 },
|
||||
{ 0x05ab, 0x05ac, 230 },
|
||||
{ 0x05ad, 0x05ad, 222 },
|
||||
{ 0x05ae, 0x05ae, 228 },
|
||||
{ 0x05af, 0x05af, 230 },
|
||||
{ 0x05b0, 0x05b0, 10 },
|
||||
{ 0x05b1, 0x05b1, 11 },
|
||||
{ 0x05b2, 0x05b2, 12 },
|
||||
{ 0x05b3, 0x05b3, 13 },
|
||||
{ 0x05b4, 0x05b4, 14 },
|
||||
{ 0x05b5, 0x05b5, 15 },
|
||||
{ 0x05b6, 0x05b6, 16 },
|
||||
{ 0x05b7, 0x05b7, 17 },
|
||||
{ 0x05b8, 0x05b8, 18 },
|
||||
{ 0x05b9, 0x05ba, 19 },
|
||||
{ 0x05bb, 0x05bb, 20 },
|
||||
{ 0x05bc, 0x05bc, 21 },
|
||||
{ 0x05bd, 0x05bd, 22 },
|
||||
{ 0x05bf, 0x05bf, 23 },
|
||||
{ 0x05c1, 0x05c1, 24 },
|
||||
{ 0x05c2, 0x05c2, 25 },
|
||||
{ 0x05c4, 0x05c4, 230 },
|
||||
{ 0x05c5, 0x05c5, 220 },
|
||||
{ 0x05c7, 0x05c7, 18 },
|
||||
{ 0x0610, 0x0617, 230 },
|
||||
{ 0x0618, 0x0618, 30 },
|
||||
{ 0x0619, 0x0619, 31 },
|
||||
{ 0x061a, 0x061a, 32 },
|
||||
{ 0x064b, 0x064b, 27 },
|
||||
{ 0x064c, 0x064c, 28 },
|
||||
{ 0x064d, 0x064d, 29 },
|
||||
{ 0x064e, 0x064e, 30 },
|
||||
{ 0x064f, 0x064f, 31 },
|
||||
{ 0x0650, 0x0650, 32 },
|
||||
{ 0x0651, 0x0651, 33 },
|
||||
{ 0x0652, 0x0652, 34 },
|
||||
{ 0x0653, 0x0654, 230 },
|
||||
{ 0x0655, 0x0656, 220 },
|
||||
{ 0x0657, 0x065b, 230 },
|
||||
{ 0x065c, 0x065c, 220 },
|
||||
{ 0x065d, 0x065e, 230 },
|
||||
{ 0x065f, 0x065f, 220 },
|
||||
{ 0x0670, 0x0670, 35 },
|
||||
{ 0x06d6, 0x06dc, 230 },
|
||||
{ 0x06df, 0x06e2, 230 },
|
||||
{ 0x06e3, 0x06e3, 220 },
|
||||
{ 0x06e4, 0x06e4, 230 },
|
||||
{ 0x06e7, 0x06e8, 230 },
|
||||
{ 0x06ea, 0x06ea, 220 },
|
||||
{ 0x06eb, 0x06ec, 230 },
|
||||
{ 0x06ed, 0x06ed, 220 },
|
||||
{ 0x0711, 0x0711, 36 },
|
||||
{ 0x0730, 0x0730, 230 },
|
||||
{ 0x0731, 0x0731, 220 },
|
||||
{ 0x0732, 0x0733, 230 },
|
||||
{ 0x0734, 0x0734, 220 },
|
||||
{ 0x0735, 0x0736, 230 },
|
||||
{ 0x0737, 0x0739, 220 },
|
||||
{ 0x073a, 0x073a, 230 },
|
||||
{ 0x073b, 0x073c, 220 },
|
||||
{ 0x073d, 0x073d, 230 },
|
||||
{ 0x073e, 0x073e, 220 },
|
||||
{ 0x073f, 0x0741, 230 },
|
||||
{ 0x0742, 0x0742, 220 },
|
||||
{ 0x0743, 0x0743, 230 },
|
||||
{ 0x0744, 0x0744, 220 },
|
||||
{ 0x0745, 0x0745, 230 },
|
||||
{ 0x0746, 0x0746, 220 },
|
||||
{ 0x0747, 0x0747, 230 },
|
||||
{ 0x0748, 0x0748, 220 },
|
||||
{ 0x0749, 0x074a, 230 },
|
||||
{ 0x07eb, 0x07f1, 230 },
|
||||
{ 0x07f2, 0x07f2, 220 },
|
||||
{ 0x07f3, 0x07f3, 230 },
|
||||
{ 0x07fd, 0x07fd, 220 },
|
||||
{ 0x0816, 0x0819, 230 },
|
||||
{ 0x081b, 0x0823, 230 },
|
||||
{ 0x0825, 0x0827, 230 },
|
||||
{ 0x0829, 0x082d, 230 },
|
||||
{ 0x0859, 0x085b, 220 },
|
||||
{ 0x08d3, 0x08d3, 220 },
|
||||
{ 0x08d4, 0x08e1, 230 },
|
||||
{ 0x08e3, 0x08e3, 220 },
|
||||
{ 0x08e4, 0x08e5, 230 },
|
||||
{ 0x08e6, 0x08e6, 220 },
|
||||
{ 0x08e7, 0x08e8, 230 },
|
||||
{ 0x08e9, 0x08e9, 220 },
|
||||
{ 0x08ea, 0x08ec, 230 },
|
||||
{ 0x08ed, 0x08ef, 220 },
|
||||
{ 0x08f0, 0x08f0, 27 },
|
||||
{ 0x08f1, 0x08f1, 28 },
|
||||
{ 0x08f2, 0x08f2, 29 },
|
||||
{ 0x08f3, 0x08f5, 230 },
|
||||
{ 0x08f6, 0x08f6, 220 },
|
||||
{ 0x08f7, 0x08f8, 230 },
|
||||
{ 0x08f9, 0x08fa, 220 },
|
||||
{ 0x08fb, 0x08ff, 230 },
|
||||
{ 0x093c, 0x093c, 7 },
|
||||
{ 0x094d, 0x094d, 9 },
|
||||
{ 0x0951, 0x0951, 230 },
|
||||
{ 0x0952, 0x0952, 220 },
|
||||
{ 0x0953, 0x0954, 230 },
|
||||
{ 0x09bc, 0x09bc, 7 },
|
||||
{ 0x09cd, 0x09cd, 9 },
|
||||
{ 0x09fe, 0x09fe, 230 },
|
||||
{ 0x0a3c, 0x0a3c, 7 },
|
||||
{ 0x0a4d, 0x0a4d, 9 },
|
||||
{ 0x0abc, 0x0abc, 7 },
|
||||
{ 0x0acd, 0x0acd, 9 },
|
||||
{ 0x0b3c, 0x0b3c, 7 },
|
||||
{ 0x0b4d, 0x0b4d, 9 },
|
||||
{ 0x0bcd, 0x0bcd, 9 },
|
||||
{ 0x0c4d, 0x0c4d, 9 },
|
||||
{ 0x0c55, 0x0c55, 84 },
|
||||
{ 0x0c56, 0x0c56, 91 },
|
||||
{ 0x0cbc, 0x0cbc, 7 },
|
||||
{ 0x0ccd, 0x0ccd, 9 },
|
||||
{ 0x0d3b, 0x0d3c, 9 },
|
||||
{ 0x0d4d, 0x0d4d, 9 },
|
||||
{ 0x0dca, 0x0dca, 9 },
|
||||
{ 0x0e38, 0x0e39, 103 },
|
||||
{ 0x0e3a, 0x0e3a, 9 },
|
||||
{ 0x0e48, 0x0e4b, 107 },
|
||||
{ 0x0eb8, 0x0eb9, 118 },
|
||||
{ 0x0eba, 0x0eba, 9 },
|
||||
{ 0x0ec8, 0x0ecb, 122 },
|
||||
{ 0x0f18, 0x0f19, 220 },
|
||||
{ 0x0f35, 0x0f35, 220 },
|
||||
{ 0x0f37, 0x0f37, 220 },
|
||||
{ 0x0f39, 0x0f39, 216 },
|
||||
{ 0x0f71, 0x0f71, 129 },
|
||||
{ 0x0f72, 0x0f72, 130 },
|
||||
{ 0x0f74, 0x0f74, 132 },
|
||||
{ 0x0f7a, 0x0f7d, 130 },
|
||||
{ 0x0f80, 0x0f80, 130 },
|
||||
{ 0x0f82, 0x0f83, 230 },
|
||||
{ 0x0f84, 0x0f84, 9 },
|
||||
{ 0x0f86, 0x0f87, 230 },
|
||||
{ 0x0fc6, 0x0fc6, 220 },
|
||||
{ 0x1037, 0x1037, 7 },
|
||||
{ 0x1039, 0x103a, 9 },
|
||||
{ 0x108d, 0x108d, 220 },
|
||||
{ 0x135d, 0x135f, 230 },
|
||||
{ 0x1714, 0x1714, 9 },
|
||||
{ 0x1734, 0x1734, 9 },
|
||||
{ 0x17d2, 0x17d2, 9 },
|
||||
{ 0x17dd, 0x17dd, 230 },
|
||||
{ 0x18a9, 0x18a9, 228 },
|
||||
{ 0x1939, 0x1939, 222 },
|
||||
{ 0x193a, 0x193a, 230 },
|
||||
{ 0x193b, 0x193b, 220 },
|
||||
{ 0x1a17, 0x1a17, 230 },
|
||||
{ 0x1a18, 0x1a18, 220 },
|
||||
{ 0x1a60, 0x1a60, 9 },
|
||||
{ 0x1a75, 0x1a7c, 230 },
|
||||
{ 0x1a7f, 0x1a7f, 220 },
|
||||
{ 0x1ab0, 0x1ab4, 230 },
|
||||
{ 0x1ab5, 0x1aba, 220 },
|
||||
{ 0x1abb, 0x1abc, 230 },
|
||||
{ 0x1abd, 0x1abd, 220 },
|
||||
{ 0x1b34, 0x1b34, 7 },
|
||||
{ 0x1b44, 0x1b44, 9 },
|
||||
{ 0x1b6b, 0x1b6b, 230 },
|
||||
{ 0x1b6c, 0x1b6c, 220 },
|
||||
{ 0x1b6d, 0x1b73, 230 },
|
||||
{ 0x1baa, 0x1baa, 9 },
|
||||
{ 0x1bab, 0x1bab, 9 },
|
||||
{ 0x1be6, 0x1be6, 7 },
|
||||
{ 0x1bf2, 0x1bf3, 9 },
|
||||
{ 0x1c37, 0x1c37, 7 },
|
||||
{ 0x1cd0, 0x1cd2, 230 },
|
||||
{ 0x1cd4, 0x1cd4, 1 },
|
||||
{ 0x1cd5, 0x1cd9, 220 },
|
||||
{ 0x1cda, 0x1cdb, 230 },
|
||||
{ 0x1cdc, 0x1cdf, 220 },
|
||||
{ 0x1ce0, 0x1ce0, 230 },
|
||||
{ 0x1ce2, 0x1ce8, 1 },
|
||||
{ 0x1ced, 0x1ced, 220 },
|
||||
{ 0x1cf4, 0x1cf4, 230 },
|
||||
{ 0x1cf8, 0x1cf9, 230 },
|
||||
{ 0x1dc0, 0x1dc1, 230 },
|
||||
{ 0x1dc2, 0x1dc2, 220 },
|
||||
{ 0x1dc3, 0x1dc9, 230 },
|
||||
{ 0x1dca, 0x1dca, 220 },
|
||||
{ 0x1dcb, 0x1dcc, 230 },
|
||||
{ 0x1dcd, 0x1dcd, 234 },
|
||||
{ 0x1dce, 0x1dce, 214 },
|
||||
{ 0x1dcf, 0x1dcf, 220 },
|
||||
{ 0x1dd0, 0x1dd0, 202 },
|
||||
{ 0x1dd1, 0x1df5, 230 },
|
||||
{ 0x1df6, 0x1df6, 232 },
|
||||
{ 0x1df7, 0x1df8, 228 },
|
||||
{ 0x1df9, 0x1df9, 220 },
|
||||
{ 0x1dfb, 0x1dfb, 230 },
|
||||
{ 0x1dfc, 0x1dfc, 233 },
|
||||
{ 0x1dfd, 0x1dfd, 220 },
|
||||
{ 0x1dfe, 0x1dfe, 230 },
|
||||
{ 0x1dff, 0x1dff, 220 },
|
||||
{ 0x20d0, 0x20d1, 230 },
|
||||
{ 0x20d2, 0x20d3, 1 },
|
||||
{ 0x20d4, 0x20d7, 230 },
|
||||
{ 0x20d8, 0x20da, 1 },
|
||||
{ 0x20db, 0x20dc, 230 },
|
||||
{ 0x20e1, 0x20e1, 230 },
|
||||
{ 0x20e5, 0x20e6, 1 },
|
||||
{ 0x20e7, 0x20e7, 230 },
|
||||
{ 0x20e8, 0x20e8, 220 },
|
||||
{ 0x20e9, 0x20e9, 230 },
|
||||
{ 0x20ea, 0x20eb, 1 },
|
||||
{ 0x20ec, 0x20ef, 220 },
|
||||
{ 0x20f0, 0x20f0, 230 },
|
||||
{ 0x2cef, 0x2cf1, 230 },
|
||||
{ 0x2d7f, 0x2d7f, 9 },
|
||||
{ 0x2de0, 0x2dff, 230 },
|
||||
{ 0x302a, 0x302a, 218 },
|
||||
{ 0x302b, 0x302b, 228 },
|
||||
{ 0x302c, 0x302c, 232 },
|
||||
{ 0x302d, 0x302d, 222 },
|
||||
{ 0x302e, 0x302f, 224 },
|
||||
{ 0x3099, 0x309a, 8 },
|
||||
{ 0xa66f, 0xa66f, 230 },
|
||||
{ 0xa674, 0xa67d, 230 },
|
||||
{ 0xa69e, 0xa69f, 230 },
|
||||
{ 0xa6f0, 0xa6f1, 230 },
|
||||
{ 0xa806, 0xa806, 9 },
|
||||
{ 0xa8c4, 0xa8c4, 9 },
|
||||
{ 0xa8e0, 0xa8f1, 230 },
|
||||
{ 0xa92b, 0xa92d, 220 },
|
||||
{ 0xa953, 0xa953, 9 },
|
||||
{ 0xa9b3, 0xa9b3, 7 },
|
||||
{ 0xa9c0, 0xa9c0, 9 },
|
||||
{ 0xaab0, 0xaab0, 230 },
|
||||
{ 0xaab2, 0xaab3, 230 },
|
||||
{ 0xaab4, 0xaab4, 220 },
|
||||
{ 0xaab7, 0xaab8, 230 },
|
||||
{ 0xaabe, 0xaabf, 230 },
|
||||
{ 0xaac1, 0xaac1, 230 },
|
||||
{ 0xaaf6, 0xaaf6, 9 },
|
||||
{ 0xabed, 0xabed, 9 },
|
||||
{ 0xfb1e, 0xfb1e, 26 },
|
||||
{ 0xfe20, 0xfe26, 230 },
|
||||
{ 0xfe27, 0xfe2d, 220 },
|
||||
{ 0xfe2e, 0xfe2f, 230 },
|
||||
};
|
||||
|
||||
/*
|
||||
* Some HFS Plus catalog names contain characters whose canonical
|
||||
* decomposition either was not yet defined, or was later corrected, in
|
||||
* the version of Unicode this file's tables above were generated from
|
||||
* (Apple Technote #1150, itself derived from pre-Unicode-3.0/pre-"Jaguar"
|
||||
* (Mac OS X 10.2) data). macOS's own fsck_hfs has carried a permanent
|
||||
* "FixDecomps" fixup for exactly this since 2002 (see
|
||||
* fsck_hfs.tproj/dfalib/CatalogCheck.c and DecompData.h in the hfsprogs /
|
||||
* diskdev_cmds sources) and flags any catalog name it finds still using
|
||||
* the old form as "Illegal name". The two tables and functions below are
|
||||
* a port of that same fixup data, decoded from hfsprogs 540.1's
|
||||
* DecompData.h, so this driver stores names the same way macOS has since
|
||||
* 10.2 rather than the way the original HFS Plus format documentation
|
||||
* described in 2001.
|
||||
*/
|
||||
|
||||
/* Category A: single code point -> missing canonical decomposition */
|
||||
/* 44 entries */
|
||||
struct hfsplus_legacy_decomp hfsplus_legacy_decomp_table[] = {
|
||||
{ 0x01f8, 2, { 0x004e, 0x0300 } },
|
||||
{ 0x01f9, 2, { 0x006e, 0x0300 } },
|
||||
{ 0x0218, 2, { 0x0053, 0x0326 } },
|
||||
{ 0x0219, 2, { 0x0073, 0x0326 } },
|
||||
{ 0x021a, 2, { 0x0054, 0x0326 } },
|
||||
{ 0x021b, 2, { 0x0074, 0x0326 } },
|
||||
{ 0x021e, 2, { 0x0048, 0x030c } },
|
||||
{ 0x021f, 2, { 0x0068, 0x030c } },
|
||||
{ 0x0226, 2, { 0x0041, 0x0307 } },
|
||||
{ 0x0227, 2, { 0x0061, 0x0307 } },
|
||||
{ 0x0228, 2, { 0x0045, 0x0327 } },
|
||||
{ 0x0229, 2, { 0x0065, 0x0327 } },
|
||||
{ 0x022a, 3, { 0x004f, 0x0308, 0x0304 } },
|
||||
{ 0x022b, 3, { 0x006f, 0x0308, 0x0304 } },
|
||||
{ 0x022c, 3, { 0x004f, 0x0303, 0x0304 } },
|
||||
{ 0x022d, 3, { 0x006f, 0x0303, 0x0304 } },
|
||||
{ 0x022e, 2, { 0x004f, 0x0307 } },
|
||||
{ 0x022f, 2, { 0x006f, 0x0307 } },
|
||||
{ 0x0230, 3, { 0x004f, 0x0307, 0x0304 } },
|
||||
{ 0x0231, 3, { 0x006f, 0x0307, 0x0304 } },
|
||||
{ 0x0232, 2, { 0x0059, 0x0304 } },
|
||||
{ 0x0233, 2, { 0x0079, 0x0304 } },
|
||||
{ 0x0400, 2, { 0x0415, 0x0300 } },
|
||||
{ 0x040d, 2, { 0x0418, 0x0300 } },
|
||||
{ 0x0450, 2, { 0x0435, 0x0300 } },
|
||||
{ 0x045d, 2, { 0x0438, 0x0300 } },
|
||||
{ 0x04ec, 2, { 0x042d, 0x0308 } },
|
||||
{ 0x04ed, 2, { 0x044d, 0x0308 } },
|
||||
{ 0x0622, 2, { 0x0627, 0x0653 } },
|
||||
{ 0x0623, 2, { 0x0627, 0x0654 } },
|
||||
{ 0x0624, 2, { 0x0648, 0x0654 } },
|
||||
{ 0x0625, 2, { 0x0627, 0x0655 } },
|
||||
{ 0x0626, 2, { 0x064a, 0x0654 } },
|
||||
{ 0x06c0, 2, { 0x06d5, 0x0654 } },
|
||||
{ 0x06c2, 2, { 0x06c1, 0x0654 } },
|
||||
{ 0x06d3, 2, { 0x06d2, 0x0654 } },
|
||||
{ 0x0a33, 2, { 0x0a32, 0x0a3c } },
|
||||
{ 0x0a36, 2, { 0x0a38, 0x0a3c } },
|
||||
{ 0x0dda, 2, { 0x0dd9, 0x0dca } },
|
||||
{ 0x0ddc, 2, { 0x0dd9, 0x0dcf } },
|
||||
{ 0x0ddd, 3, { 0x0dd9, 0x0dcf, 0x0dca } },
|
||||
{ 0x0dde, 2, { 0x0dd9, 0x0ddf } },
|
||||
{ 0x1026, 2, { 0x1025, 0x102e } },
|
||||
{ 0xfb1d, 2, { 0x05d9, 0x05b4 } },
|
||||
};
|
||||
|
||||
/* Category B: 2-3 code point sequence -> corrected replacement */
|
||||
/* 25 entries */
|
||||
struct hfsplus_legacy_seq_fixup hfsplus_legacy_seq_fixups[] = {
|
||||
{ 2, { 0x00a8, 0x030d }, 2, { 0x00a8, 0x0301 } },
|
||||
{ 2, { 0x0306, 0x0307 }, 1, { 0x0310 } },
|
||||
{ 2, { 0x0308, 0x030d }, 2, { 0x0308, 0x0301 } },
|
||||
{ 2, { 0x0391, 0x030d }, 2, { 0x0391, 0x0301 } },
|
||||
{ 2, { 0x0395, 0x030d }, 2, { 0x0395, 0x0301 } },
|
||||
{ 2, { 0x0397, 0x030d }, 2, { 0x0397, 0x0301 } },
|
||||
{ 2, { 0x0399, 0x030d }, 2, { 0x0399, 0x0301 } },
|
||||
{ 2, { 0x039f, 0x030d }, 2, { 0x039f, 0x0301 } },
|
||||
{ 2, { 0x03a5, 0x030d }, 2, { 0x03a5, 0x0301 } },
|
||||
{ 2, { 0x03a9, 0x030d }, 2, { 0x03a9, 0x0301 } },
|
||||
{ 2, { 0x03b1, 0x030d }, 2, { 0x03b1, 0x0301 } },
|
||||
{ 2, { 0x03b5, 0x030d }, 2, { 0x03b5, 0x0301 } },
|
||||
{ 2, { 0x03b7, 0x030d }, 2, { 0x03b7, 0x0301 } },
|
||||
{ 2, { 0x03b9, 0x030d }, 2, { 0x03b9, 0x0301 } },
|
||||
{ 2, { 0x03bf, 0x030d }, 2, { 0x03bf, 0x0301 } },
|
||||
{ 2, { 0x03c5, 0x030d }, 2, { 0x03c5, 0x0301 } },
|
||||
{ 2, { 0x03c9, 0x030d }, 2, { 0x03c9, 0x0301 } },
|
||||
{ 2, { 0x03d2, 0x030d }, 2, { 0x03d2, 0x0301 } },
|
||||
{ 2, { 0x09ac, 0x09bc }, 1, { 0x09b0 } },
|
||||
{ 2, { 0x0a21, 0x0a3c }, 1, { 0x0a5c } },
|
||||
{ 2, { 0x0b2f, 0x0b3c }, 1, { 0x0b5f } },
|
||||
{ 2, { 0x0e4d, 0x0e32 }, 1, { 0x0e33 } },
|
||||
{ 2, { 0x0ecd, 0x0eb2 }, 1, { 0x0eb3 } },
|
||||
{ 3, { 0x0fb2, 0x0f80, 0x0f71 }, 1, { 0x0f77 } },
|
||||
{ 3, { 0x0fb3, 0x0f80, 0x0f71 }, 1, { 0x0f79 } },
|
||||
};
|
||||
|
||||
/*
|
||||
* Look up the Unicode canonical combining class of a BMP code point.
|
||||
* Returns 0 ("Not_Reordered") for any code point not listed in the table,
|
||||
* which includes every ordinary base character.
|
||||
*/
|
||||
u8 hfsplus_combining_class(u16 c)
|
||||
{
|
||||
int lo = 0, hi = ARRAY_SIZE(hfsplus_ccc_table) - 1;
|
||||
|
||||
while (lo <= hi) {
|
||||
int mid = (lo + hi) / 2;
|
||||
const struct hfsplus_ccc_range *r = &hfsplus_ccc_table[mid];
|
||||
|
||||
if (c < r->first)
|
||||
hi = mid - 1;
|
||||
else if (c > r->last)
|
||||
lo = mid + 1;
|
||||
else
|
||||
return r->combining_class;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up the corrected canonical decomposition for a single BMP code
|
||||
* point that Apple Technote #1150's own decomposition table (above)
|
||||
* lacks. Returns NULL (and leaves *size alone) if @uc isn't one of these.
|
||||
*/
|
||||
const u16 *hfsplus_legacy_decompose(u16 uc, int *size)
|
||||
{
|
||||
int lo = 0, hi = ARRAY_SIZE(hfsplus_legacy_decomp_table) - 1;
|
||||
|
||||
while (lo <= hi) {
|
||||
int mid = (lo + hi) / 2;
|
||||
const struct hfsplus_legacy_decomp *e =
|
||||
&hfsplus_legacy_decomp_table[mid];
|
||||
|
||||
if (uc < e->uc)
|
||||
hi = mid - 1;
|
||||
else if (uc > e->uc)
|
||||
lo = mid + 1;
|
||||
else {
|
||||
*size = e->len;
|
||||
return e->repl;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan an already decomposed and canonically-reordered code unit buffer
|
||||
* for any of the short legacy sequences above and replace them in place
|
||||
* with their corrected form. @len is updated to the buffer's new length.
|
||||
*/
|
||||
void hfsplus_fixup_legacy_sequences(u16 *buf, int *len)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (i < *len) {
|
||||
unsigned int j;
|
||||
bool matched = false;
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(hfsplus_legacy_seq_fixups); j++) {
|
||||
struct hfsplus_legacy_seq_fixup *f =
|
||||
&hfsplus_legacy_seq_fixups[j];
|
||||
u16 *src, *dst;
|
||||
size_t copy_len;
|
||||
int k;
|
||||
|
||||
if (i + f->match_len > *len)
|
||||
continue;
|
||||
for (k = 0; k < f->match_len; k++)
|
||||
if (buf[i + k] != f->match[k])
|
||||
break;
|
||||
if (k != f->match_len)
|
||||
continue;
|
||||
|
||||
dst = &buf[i + f->repl_len];
|
||||
src = &buf[i + f->match_len];
|
||||
copy_len = (*len - i - f->match_len) * sizeof(*buf);
|
||||
memmove(dst, src, copy_len);
|
||||
|
||||
dst = &buf[i];
|
||||
src = f->repl;
|
||||
copy_len = f->repl_len * sizeof(*buf);
|
||||
memcpy(dst, src, copy_len);
|
||||
|
||||
*len += f->repl_len - f->match_len;
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!matched)
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "hfsplus_fs.h"
|
||||
#include "hfsplus_raw.h"
|
||||
#include "unicode.h"
|
||||
|
||||
/* Fold the case of a unicode char, given the 16 bit value */
|
||||
/* Returns folded char, or 0 if ignorable */
|
||||
|
|
@ -441,45 +442,264 @@ static u16 *decompose_unichar(wchar_t uc, int *size, u16 *hangul_buffer)
|
|||
*size = hfsplus_try_decompose_hangul(uc, result);
|
||||
if (*size == 0)
|
||||
result = hfsplus_decompose_nonhangul(uc, size);
|
||||
if (!result) {
|
||||
/*
|
||||
* Not every character with a canonical decomposition is in
|
||||
* Apple Technote #1150's own table above; a small, fixed
|
||||
* set was only added to the decomposition standard (or had
|
||||
* its decomposition corrected) after that table was
|
||||
* generated. hfsplus_legacy_decompose() covers those.
|
||||
*/
|
||||
const u16 *legacy = hfsplus_legacy_decompose(uc, size);
|
||||
|
||||
if (legacy)
|
||||
result = memcpy(hangul_buffer, legacy,
|
||||
*size * sizeof(*legacy));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply the Unicode Canonical Ordering Algorithm to a decomposed name held
|
||||
* as plain host-order code units: within each maximal run of characters
|
||||
* that have a nonzero combining class, stable-sort the run into ascending
|
||||
* combining-class order. A character with combining class 0 always starts
|
||||
* a new run and is never itself reordered.
|
||||
*
|
||||
* hfsplus_decompose_table decomposes each source character on its own; it
|
||||
* says nothing about how the decompositions of two different source
|
||||
* characters should be ordered relative to each other when both produce
|
||||
* combining marks that end up adjacent. Without this pass, such a
|
||||
* sequence can be stored in an order that macOS's own fsck_hfs
|
||||
* (FixDecomps() in CatalogCheck.c) considers illegal, even though every
|
||||
* individual character was decomposed correctly.
|
||||
*/
|
||||
static void hfsplus_canonical_reorder(u16 *ustr, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 1; i < len; i++) {
|
||||
u8 cls = hfsplus_combining_class(ustr[i]);
|
||||
int j = i;
|
||||
|
||||
if (!cls)
|
||||
continue;
|
||||
|
||||
while (j > 0) {
|
||||
u8 prev_cls = hfsplus_combining_class(ustr[j - 1]);
|
||||
u16 tmp;
|
||||
|
||||
if (!prev_cls || prev_cls <= cls)
|
||||
break;
|
||||
|
||||
tmp = ustr[j];
|
||||
ustr[j] = ustr[j - 1];
|
||||
ustr[j - 1] = tmp;
|
||||
j--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define HFSPLUS_HANGUL_MAX_JAMO (3) /* L + V + optional T */
|
||||
|
||||
static_assert(HFSPLUS_HANGUL_MAX_JAMO >= HFSPLUS_LEGACY_DECOMP_MAX_LEN,
|
||||
"hangul_buffer must fit the longest legacy decomposition too");
|
||||
|
||||
/*
|
||||
* Decompose and canonically reorder an entire Linux name, as plain
|
||||
* host-order code units. hfsplus_asc2uni(), hfsplus_hash_dentry() and
|
||||
* hfsplus_compare_dentry() all go through this so that storage, hashing
|
||||
* and comparison always agree on what a given name canonicalizes to.
|
||||
*
|
||||
* @out must hold at least @max_len entries, which must not exceed
|
||||
* HFSPLUS_MAX_STRLEN. Returns the number of code units written. If
|
||||
* @consumed is non-NULL, it is set to the number of input bytes actually
|
||||
* consumed, which is less than @len when @out fills up first.
|
||||
*/
|
||||
static int hfsplus_decompose_str(struct super_block *sb, const char *astr,
|
||||
int len, int max_len, int name_type,
|
||||
u16 *out, int *consumed)
|
||||
{
|
||||
int decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
||||
const char *start = astr;
|
||||
int outlen = 0;
|
||||
|
||||
while (outlen < max_len && len > 0) {
|
||||
u16 *dstr;
|
||||
u16 dhangul[HFSPLUS_HANGUL_MAX_JAMO];
|
||||
int dsize, size;
|
||||
wchar_t c;
|
||||
|
||||
size = asc2unichar(sb, astr, len, &c, name_type);
|
||||
|
||||
dstr = decompose ? decompose_unichar(c, &dsize, dhangul) : NULL;
|
||||
if (dstr) {
|
||||
if (outlen + dsize > max_len)
|
||||
break;
|
||||
do {
|
||||
out[outlen++] = *dstr++;
|
||||
} while (--dsize > 0);
|
||||
} else {
|
||||
out[outlen++] = c;
|
||||
}
|
||||
|
||||
astr += size;
|
||||
len -= size;
|
||||
}
|
||||
|
||||
hfsplus_canonical_reorder(out, outlen);
|
||||
hfsplus_fixup_legacy_sequences(out, &outlen);
|
||||
if (consumed)
|
||||
*consumed = astr - start;
|
||||
return outlen;
|
||||
}
|
||||
|
||||
int hfsplus_asc2uni(struct super_block *sb,
|
||||
struct hfsplus_unistr *ustr, int max_unistr_len,
|
||||
const char *astr, int len, int name_type)
|
||||
{
|
||||
int size, dsize, decompose;
|
||||
u16 *dstr, outlen = 0;
|
||||
wchar_t c;
|
||||
u16 dhangul[3];
|
||||
u16 buf[HFSPLUS_MAX_STRLEN];
|
||||
int outlen, i, consumed;
|
||||
|
||||
decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
||||
while (outlen < max_unistr_len && len > 0) {
|
||||
size = asc2unichar(sb, astr, len, &c, name_type);
|
||||
if (max_unistr_len > HFSPLUS_MAX_STRLEN)
|
||||
max_unistr_len = HFSPLUS_MAX_STRLEN;
|
||||
|
||||
if (decompose)
|
||||
dstr = decompose_unichar(c, &dsize, dhangul);
|
||||
else
|
||||
dstr = NULL;
|
||||
if (dstr) {
|
||||
if (outlen + dsize > max_unistr_len)
|
||||
break;
|
||||
do {
|
||||
ustr->unicode[outlen++] = cpu_to_be16(*dstr++);
|
||||
} while (--dsize > 0);
|
||||
} else
|
||||
ustr->unicode[outlen++] = cpu_to_be16(c);
|
||||
|
||||
astr += size;
|
||||
len -= size;
|
||||
}
|
||||
outlen = hfsplus_decompose_str(sb, astr, len, max_unistr_len,
|
||||
name_type, buf, &consumed);
|
||||
for (i = 0; i < outlen; i++)
|
||||
ustr->unicode[i] = cpu_to_be16(buf[i]);
|
||||
ustr->length = cpu_to_be16(outlen);
|
||||
if (len > 0)
|
||||
|
||||
if (consumed < len)
|
||||
return -ENAMETOOLONG;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(hfsplus_asc2uni);
|
||||
|
||||
/*
|
||||
* Maximum length of a single maximal run of nonzero-combining-class code
|
||||
* units that hfsplus_decompose_iter_next() below will canonically
|
||||
* reorder. Every code unit with combining class 0 starts a new run, so
|
||||
* this only bounds how many *consecutive* combining marks between two
|
||||
* base characters get sorted - real text, and even deliberately
|
||||
* adversarial "Zalgo" text, essentially never approaches this. Keeping
|
||||
* it small means hfsplus_hash_dentry() and hfsplus_compare_dentry() only
|
||||
* ever need a tiny amount of lookahead state, rather than buffering an
|
||||
* entire (up to 255-unit) name.
|
||||
*/
|
||||
#define HFSPLUS_CCC_RUN_MAX 32
|
||||
|
||||
/*
|
||||
* Iterator that produces the canonically-ordered, decomposed code units
|
||||
* of a Linux name one at a time, without materializing the whole name.
|
||||
* hfsplus_hash_dentry() and hfsplus_compare_dentry() use this so that
|
||||
* hashing and comparison always agree with what hfsplus_asc2uni() would
|
||||
* actually store, while still comparing lazily (stopping at the first
|
||||
* difference) the way this code did before canonical reordering existed.
|
||||
*/
|
||||
struct hfsplus_decompose_iter {
|
||||
struct super_block *sb;
|
||||
const char *astr;
|
||||
int len;
|
||||
int name_type;
|
||||
int decompose;
|
||||
|
||||
u16 run[HFSPLUS_CCC_RUN_MAX];
|
||||
int run_len;
|
||||
int run_pos;
|
||||
};
|
||||
|
||||
static void hfsplus_decompose_iter_init(struct hfsplus_decompose_iter *it,
|
||||
struct super_block *sb,
|
||||
const char *astr, int len,
|
||||
int name_type)
|
||||
{
|
||||
it->sb = sb;
|
||||
it->astr = astr;
|
||||
it->len = len;
|
||||
it->name_type = name_type;
|
||||
it->decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
||||
it->run_len = 0;
|
||||
it->run_pos = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gather the next maximal run of nonzero-combining-class code units
|
||||
* (starting with whatever character comes next, base or not) and
|
||||
* canonically reorder just that run. A character is only included once
|
||||
* we know the class of the first unit it produces, so decoding it is
|
||||
* speculative until that's decided.
|
||||
*/
|
||||
static bool hfsplus_decompose_iter_refill(struct hfsplus_decompose_iter *it)
|
||||
{
|
||||
it->run_pos = 0;
|
||||
it->run_len = 0;
|
||||
|
||||
while (it->len > 0) {
|
||||
u16 *dstr;
|
||||
u16 dhangul[HFSPLUS_HANGUL_MAX_JAMO];
|
||||
int dsize, size;
|
||||
wchar_t c;
|
||||
|
||||
size = asc2unichar(it->sb, it->astr, it->len, &c,
|
||||
it->name_type);
|
||||
|
||||
dstr = it->decompose ?
|
||||
decompose_unichar(c, &dsize, dhangul) : NULL;
|
||||
if (!dstr) {
|
||||
dhangul[0] = c;
|
||||
dstr = dhangul;
|
||||
dsize = 1;
|
||||
}
|
||||
|
||||
if (it->run_len > 0 && !hfsplus_combining_class(dstr[0]))
|
||||
break;
|
||||
|
||||
if (it->run_len + dsize > HFSPLUS_CCC_RUN_MAX)
|
||||
break;
|
||||
|
||||
it->astr += size;
|
||||
it->len -= size;
|
||||
|
||||
do {
|
||||
it->run[it->run_len++] = *dstr++;
|
||||
} while (--dsize > 0);
|
||||
}
|
||||
|
||||
hfsplus_canonical_reorder(it->run, it->run_len);
|
||||
hfsplus_fixup_legacy_sequences(it->run, &it->run_len);
|
||||
return it->run_len > 0;
|
||||
}
|
||||
|
||||
/* Returns the next code unit, or a negative value once @it is exhausted. */
|
||||
static int hfsplus_decompose_iter_next(struct hfsplus_decompose_iter *it)
|
||||
{
|
||||
if (it->run_pos >= it->run_len && !hfsplus_decompose_iter_refill(it))
|
||||
return -1;
|
||||
return it->run[it->run_pos++];
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the next code unit that matters for comparison/hashing: folded
|
||||
* if @casefold, with any character folding to 0 ("ignorable") skipped
|
||||
* entirely. Returns a negative value once @it is exhausted.
|
||||
*/
|
||||
static int hfsplus_decompose_iter_next_folded(struct hfsplus_decompose_iter *it,
|
||||
int casefold)
|
||||
{
|
||||
int c;
|
||||
|
||||
do {
|
||||
c = hfsplus_decompose_iter_next(it);
|
||||
if (c < 0)
|
||||
return -1;
|
||||
if (casefold)
|
||||
c = case_fold(c);
|
||||
} while (casefold && !c);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hash a string to an integer as appropriate for the HFS+ filesystem.
|
||||
* Composed unicode characters are decomposed and case-folding is performed
|
||||
|
|
@ -488,45 +708,17 @@ EXPORT_SYMBOL_IF_KUNIT(hfsplus_asc2uni);
|
|||
int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str)
|
||||
{
|
||||
struct super_block *sb = dentry->d_sb;
|
||||
const char *astr;
|
||||
const u16 *dstr;
|
||||
int casefold, decompose, size, len;
|
||||
int casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
|
||||
struct hfsplus_decompose_iter it;
|
||||
unsigned long hash;
|
||||
wchar_t c;
|
||||
u16 c2;
|
||||
u16 dhangul[3];
|
||||
int c;
|
||||
|
||||
hfsplus_decompose_iter_init(&it, sb, str->name, str->len,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
|
||||
decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
||||
hash = init_name_hash(dentry);
|
||||
astr = str->name;
|
||||
len = str->len;
|
||||
while (len > 0) {
|
||||
int dsize;
|
||||
size = asc2unichar(sb, astr, len, &c, HFS_REGULAR_NAME);
|
||||
astr += size;
|
||||
len -= size;
|
||||
|
||||
if (decompose)
|
||||
dstr = decompose_unichar(c, &dsize, dhangul);
|
||||
else
|
||||
dstr = NULL;
|
||||
if (dstr) {
|
||||
do {
|
||||
c2 = *dstr++;
|
||||
if (casefold)
|
||||
c2 = case_fold(c2);
|
||||
if (!casefold || c2)
|
||||
hash = partial_name_hash(c2, hash);
|
||||
} while (--dsize > 0);
|
||||
} else {
|
||||
c2 = c;
|
||||
if (casefold)
|
||||
c2 = case_fold(c2);
|
||||
if (!casefold || c2)
|
||||
hash = partial_name_hash(c2, hash);
|
||||
}
|
||||
}
|
||||
while ((c = hfsplus_decompose_iter_next_folded(&it, casefold)) >= 0)
|
||||
hash = partial_name_hash(c, hash);
|
||||
str->hash = end_name_hash(hash);
|
||||
|
||||
return 0;
|
||||
|
|
@ -542,87 +734,21 @@ int hfsplus_compare_dentry(const struct dentry *dentry,
|
|||
unsigned int len, const char *str, const struct qstr *name)
|
||||
{
|
||||
struct super_block *sb = dentry->d_sb;
|
||||
int casefold, decompose, size;
|
||||
int dsize1, dsize2, len1, len2;
|
||||
const u16 *dstr1, *dstr2;
|
||||
const char *astr1, *astr2;
|
||||
u16 c1, c2;
|
||||
wchar_t c;
|
||||
u16 dhangul_1[3], dhangul_2[3];
|
||||
int casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
|
||||
struct hfsplus_decompose_iter it1, it2;
|
||||
|
||||
casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
|
||||
decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
||||
astr1 = str;
|
||||
len1 = len;
|
||||
astr2 = name->name;
|
||||
len2 = name->len;
|
||||
dsize1 = dsize2 = 0;
|
||||
dstr1 = dstr2 = NULL;
|
||||
hfsplus_decompose_iter_init(&it1, sb, str, len, HFS_REGULAR_NAME);
|
||||
hfsplus_decompose_iter_init(&it2, sb, name->name, name->len,
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
while (len1 > 0 && len2 > 0) {
|
||||
if (!dsize1) {
|
||||
size = asc2unichar(sb, astr1, len1, &c,
|
||||
HFS_REGULAR_NAME);
|
||||
astr1 += size;
|
||||
len1 -= size;
|
||||
while (1) {
|
||||
int c1 = hfsplus_decompose_iter_next_folded(&it1, casefold);
|
||||
int c2 = hfsplus_decompose_iter_next_folded(&it2, casefold);
|
||||
|
||||
if (decompose)
|
||||
dstr1 = decompose_unichar(c, &dsize1,
|
||||
dhangul_1);
|
||||
if (!decompose || !dstr1) {
|
||||
c1 = c;
|
||||
dstr1 = &c1;
|
||||
dsize1 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dsize2) {
|
||||
size = asc2unichar(sb, astr2, len2, &c,
|
||||
HFS_REGULAR_NAME);
|
||||
astr2 += size;
|
||||
len2 -= size;
|
||||
|
||||
if (decompose)
|
||||
dstr2 = decompose_unichar(c, &dsize2,
|
||||
dhangul_2);
|
||||
if (!decompose || !dstr2) {
|
||||
c2 = c;
|
||||
dstr2 = &c2;
|
||||
dsize2 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
c1 = *dstr1;
|
||||
c2 = *dstr2;
|
||||
if (casefold) {
|
||||
c1 = case_fold(c1);
|
||||
if (!c1) {
|
||||
dstr1++;
|
||||
dsize1--;
|
||||
continue;
|
||||
}
|
||||
c2 = case_fold(c2);
|
||||
if (!c2) {
|
||||
dstr2++;
|
||||
dsize2--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (c1 < c2)
|
||||
return -1;
|
||||
else if (c1 > c2)
|
||||
return 1;
|
||||
|
||||
dstr1++;
|
||||
dsize1--;
|
||||
dstr2++;
|
||||
dsize2--;
|
||||
if (c1 < 0 || c2 < 0)
|
||||
return c1 == c2 ? 0 : (c1 < 0 ? -1 : 1);
|
||||
if (c1 != c2)
|
||||
return c1 < c2 ? -1 : 1;
|
||||
}
|
||||
|
||||
if (len1 < len2)
|
||||
return -1;
|
||||
if (len1 > len2)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(hfsplus_compare_dentry);
|
||||
|
|
|
|||
63
fs/hfsplus/unicode.h
Normal file
63
fs/hfsplus/unicode.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Unicode related declarations
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_HFSPLUS_UNICODE_H
|
||||
#define _LINUX_HFSPLUS_UNICODE_H
|
||||
|
||||
/*
|
||||
* struct hfsplus_ccc_range - one run of consecutive BMP code points that
|
||||
* share the same Unicode canonical combining class
|
||||
* @first: the first UTF-16 code point in the run
|
||||
* @last: the last code point in the run (inclusive)
|
||||
* @combining_class: canonical combining class (ccc) shared by @first..@last
|
||||
*/
|
||||
struct hfsplus_ccc_range {
|
||||
u16 first;
|
||||
u16 last;
|
||||
u8 combining_class;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct hfsplus_legacy_decomp - corrected canonical decomposition
|
||||
* @uc: the code point (a UTF-16 code unit) to look up
|
||||
* @len: number of valid code units in @repl
|
||||
* @repl: corrected decomposition sequence (base letter + combining marks)
|
||||
*/
|
||||
struct hfsplus_legacy_decomp {
|
||||
u16 uc;
|
||||
u8 len;
|
||||
#define HFSPLUS_LEGACY_DECOMP_MAX_LEN (3)
|
||||
u16 repl[HFSPLUS_LEGACY_DECOMP_MAX_LEN];
|
||||
};
|
||||
|
||||
/*
|
||||
* struct hfsplus_legacy_seq_fixup - corrected replacement for a short
|
||||
* sequence of already-decomposed code units
|
||||
* @match_len: number of valid code units in @match
|
||||
* @match: decomposed sequence to look for in the buffer
|
||||
* @repl_len: number of valid code units in @repl
|
||||
* @repl: corrected sequence to substitute in place of @match
|
||||
*/
|
||||
struct hfsplus_legacy_seq_fixup {
|
||||
u8 match_len;
|
||||
#define HFSPLUS_LEGACY_SEQ_MAX_MATCH (3)
|
||||
u16 match[HFSPLUS_LEGACY_SEQ_MAX_MATCH];
|
||||
u8 repl_len;
|
||||
#define HFSPLUS_LEGACY_SEQ_MAX_REPL (2)
|
||||
u16 repl[HFSPLUS_LEGACY_SEQ_MAX_REPL];
|
||||
};
|
||||
|
||||
extern u16 hfsplus_case_fold_table[];
|
||||
extern u16 hfsplus_decompose_table[];
|
||||
extern u16 hfsplus_compose_table[];
|
||||
extern struct hfsplus_ccc_range hfsplus_ccc_table[];
|
||||
extern struct hfsplus_legacy_decomp hfsplus_legacy_decomp_table[];
|
||||
extern struct hfsplus_legacy_seq_fixup hfsplus_legacy_seq_fixups[];
|
||||
|
||||
u8 hfsplus_combining_class(u16 c);
|
||||
const u16 *hfsplus_legacy_decompose(u16 uc, int *size);
|
||||
void hfsplus_fixup_legacy_sequences(u16 *buf, int *len);
|
||||
|
||||
#endif /* _LINUX_HFSPLUS_UNICODE_H */
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
#include <linux/dcache.h>
|
||||
#include <linux/stringhash.h>
|
||||
#include "hfsplus_fs.h"
|
||||
#include "unicode.h"
|
||||
|
||||
struct test_mock_string_env {
|
||||
struct hfsplus_unistr str1;
|
||||
|
|
@ -947,6 +948,223 @@ static void hfsplus_asc2uni_decompose_test(struct kunit *test)
|
|||
free_mock_sb(mock_sb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Real UTF-8 <-> wchar_t conversion, mirroring what the "utf8" NLS table
|
||||
* actually does (see char2uni()/uni2char() in fs/nls/nls_utf8.c). The
|
||||
* test_char2uni()/test_uni2char() stand-ins above only handle one raw
|
||||
* byte per character, which cannot represent the combining marks (e.g.
|
||||
* U+0301, U+0323) the canonical-reordering tests below need.
|
||||
*/
|
||||
static int test_char2uni_utf8(const unsigned char *rawstring, int boundlen,
|
||||
wchar_t *uni)
|
||||
{
|
||||
unicode_t u;
|
||||
int n = utf8_to_utf32(rawstring, boundlen, &u);
|
||||
|
||||
if (n < 0 || u > MAX_WCHAR_T) {
|
||||
*uni = 0x3f; /* ? */
|
||||
return -EINVAL;
|
||||
}
|
||||
*uni = (wchar_t)u;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int test_uni2char_utf8(wchar_t uni, unsigned char *out, int boundlen)
|
||||
{
|
||||
int n = utf32_to_utf8(uni, out, boundlen);
|
||||
|
||||
if (n < 0) {
|
||||
*out = '?';
|
||||
return -EINVAL;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that hfsplus_asc2uni() brings combining marks contributed by
|
||||
* different source characters into Unicode canonical (combining-class)
|
||||
* order, instead of just storing them in whatever order they were typed.
|
||||
*
|
||||
* U+0301 (COMBINING ACUTE ACCENT) has combining class 230; U+0323
|
||||
* (COMBINING DOT BELOW) has combining class 220. Since 220 < 230,
|
||||
* canonical order requires the dot-below before the acute. A name
|
||||
* created with them typed in the "wrong" order must still end up stored
|
||||
* in canonical order - this is exactly what macOS's own fsck_hfs
|
||||
* (FixDecomps() in CatalogCheck.c) requires and flags as "Illegal name"
|
||||
* when it isn't true.
|
||||
*/
|
||||
static void hfsplus_asc2uni_combining_reorder_test(struct kunit *test)
|
||||
{
|
||||
struct test_mock_sb *mock_sb;
|
||||
struct hfsplus_unistr ustr;
|
||||
/* "e" + COMBINING ACUTE ACCENT (U+0301) + COMBINING DOT BELOW (U+0323) */
|
||||
static const char wrong_order[] = "e\xcc\x81\xcc\xa3";
|
||||
/* "e" + COMBINING DOT BELOW (U+0323) + COMBINING ACUTE ACCENT (U+0301) */
|
||||
static const char canonical_order[] = "e\xcc\xa3\xcc\x81";
|
||||
/* Two class-230 marks: GRAVE (U+0300) then ACUTE (U+0301) */
|
||||
static const char same_class[] = "e\xcc\x80\xcc\x81";
|
||||
int result;
|
||||
|
||||
mock_sb = setup_mock_sb();
|
||||
KUNIT_ASSERT_NOT_NULL(test, mock_sb);
|
||||
|
||||
mock_sb->nls.char2uni = test_char2uni_utf8;
|
||||
|
||||
/* Typed out of canonical order: must be reordered on storage. */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
|
||||
wrong_order, strlen(wrong_order),
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
|
||||
KUNIT_EXPECT_EQ(test, 'e', be16_to_cpu(ustr.unicode[0]));
|
||||
KUNIT_EXPECT_EQ(test, 0x0323, be16_to_cpu(ustr.unicode[1]));
|
||||
KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[2]));
|
||||
|
||||
/* Already in canonical order: must come out unchanged. */
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
|
||||
canonical_order, strlen(canonical_order),
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
|
||||
KUNIT_EXPECT_EQ(test, 'e', be16_to_cpu(ustr.unicode[0]));
|
||||
KUNIT_EXPECT_EQ(test, 0x0323, be16_to_cpu(ustr.unicode[1]));
|
||||
KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[2]));
|
||||
|
||||
/* Two marks of equal combining class must keep their relative
|
||||
* (input) order - the sort must be stable, not just "sorted".
|
||||
*/
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
|
||||
same_class, strlen(same_class),
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
|
||||
KUNIT_EXPECT_EQ(test, 'e', be16_to_cpu(ustr.unicode[0]));
|
||||
KUNIT_EXPECT_EQ(test, 0x0300, be16_to_cpu(ustr.unicode[1]));
|
||||
KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[2]));
|
||||
|
||||
free_mock_sb(mock_sb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that a name written with combining marks in non-canonical order
|
||||
* still reads back as valid, correct UTF-8 once reordered - i.e. that
|
||||
* hfsplus_asc2uni() and hfsplus_uni2asc_str() stay consistent with each
|
||||
* other across the reordering.
|
||||
*
|
||||
* NODECOMPOSE is set so hfsplus_uni2asc_str() doesn't also recompose
|
||||
* "e" + COMBINING DOT BELOW back into the precomposed U+1EB9 ("e"): that
|
||||
* composition behavior is real (and already covered elsewhere), but it
|
||||
* would obscure what this test is actually checking.
|
||||
*/
|
||||
static void hfsplus_unicode_combining_reorder_roundtrip_test(struct kunit *test)
|
||||
{
|
||||
struct test_mock_sb *mock_sb;
|
||||
struct hfsplus_unistr ustr;
|
||||
static const char wrong_order[] = "e\xcc\x81\xcc\xa3";
|
||||
static const char expected[] = "e\xcc\xa3\xcc\x81"; /* canonical order */
|
||||
char buf[32];
|
||||
int len = sizeof(buf);
|
||||
int result;
|
||||
|
||||
mock_sb = setup_mock_sb();
|
||||
KUNIT_ASSERT_NOT_NULL(test, mock_sb);
|
||||
|
||||
set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
|
||||
mock_sb->nls.char2uni = test_char2uni_utf8;
|
||||
mock_sb->nls.uni2char = test_uni2char_utf8;
|
||||
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
|
||||
wrong_order, strlen(wrong_order),
|
||||
HFS_REGULAR_NAME);
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
|
||||
result = hfsplus_uni2asc_str(&mock_sb->sb, &ustr, buf, &len);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, (int)strlen(expected), len);
|
||||
KUNIT_EXPECT_MEMEQ(test, expected, buf, len);
|
||||
|
||||
free_mock_sb(mock_sb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that hfsplus_asc2uni() decomposes a character using the corrected
|
||||
* (post-2002/"Jaguar") canonical decomposition even when Apple Technote
|
||||
* #1150's own decomposition table doesn't have an entry for it.
|
||||
*
|
||||
* U+01F8 (LATIN CAPITAL LETTER N WITH GRAVE) is exactly one of the
|
||||
* characters macOS's own fsck_hfs (FixDecomps() in CatalogCheck.c) has
|
||||
* flagged "Illegal name" for since 2002 when found stored undecomposed.
|
||||
*/
|
||||
static void hfsplus_asc2uni_legacy_decomp_test(struct kunit *test)
|
||||
{
|
||||
struct test_mock_sb *mock_sb;
|
||||
struct hfsplus_unistr ustr;
|
||||
static const char input[] = "\xc7\xb8"; /* U+01F8 */
|
||||
int result;
|
||||
|
||||
mock_sb = setup_mock_sb();
|
||||
KUNIT_ASSERT_NOT_NULL(test, mock_sb);
|
||||
|
||||
mock_sb->nls.char2uni = test_char2uni_utf8;
|
||||
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
|
||||
input, strlen(input), HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, 2, be16_to_cpu(ustr.length));
|
||||
KUNIT_EXPECT_EQ(test, 'N', be16_to_cpu(ustr.unicode[0]));
|
||||
KUNIT_EXPECT_EQ(test, 0x0300, be16_to_cpu(ustr.unicode[1]));
|
||||
|
||||
free_mock_sb(mock_sb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that hfsplus_asc2uni() corrects two more of fsck_hfs's known
|
||||
* legacy decomposition sequences once combining marks from independently
|
||||
* typed characters end up adjacent:
|
||||
*
|
||||
* - GREEK SMALL LETTER ALPHA (U+03B1) + COMBINING VERTICAL LINE ABOVE
|
||||
* (U+030D) must become U+03B1 + COMBINING ACUTE ACCENT (U+0301).
|
||||
* - BENGALI LETTER BA (U+09AC) + BENGALI SIGN NUKTA (U+09BC) must become
|
||||
* the single character BENGALI LETTER RA WITH MIDDLE DIAGONAL (U+09B0).
|
||||
*/
|
||||
static void hfsplus_asc2uni_legacy_seq_fixup_test(struct kunit *test)
|
||||
{
|
||||
struct test_mock_sb *mock_sb;
|
||||
struct hfsplus_unistr ustr;
|
||||
static const char greek_input[] = "\xce\xb1\xcc\x8d"; /* U+03B1 U+030D */
|
||||
static const char bengali_input[] = "\xe0\xa6\xac\xe0\xa6\xbc"; /* U+09AC U+09BC */
|
||||
int result;
|
||||
|
||||
mock_sb = setup_mock_sb();
|
||||
KUNIT_ASSERT_NOT_NULL(test, mock_sb);
|
||||
|
||||
mock_sb->nls.char2uni = test_char2uni_utf8;
|
||||
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
|
||||
greek_input, strlen(greek_input),
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, 2, be16_to_cpu(ustr.length));
|
||||
KUNIT_EXPECT_EQ(test, 0x03b1, be16_to_cpu(ustr.unicode[0]));
|
||||
KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[1]));
|
||||
|
||||
result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
|
||||
bengali_input, strlen(bengali_input),
|
||||
HFS_REGULAR_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
KUNIT_EXPECT_EQ(test, 1, be16_to_cpu(ustr.length));
|
||||
KUNIT_EXPECT_EQ(test, 0x09b0, be16_to_cpu(ustr.unicode[0]));
|
||||
|
||||
free_mock_sb(mock_sb);
|
||||
}
|
||||
|
||||
/* Mock dentry for testing hfsplus_hash_dentry */
|
||||
static struct dentry test_dentry;
|
||||
|
||||
|
|
@ -1230,6 +1448,37 @@ static void hfsplus_hash_dentry_edge_cases_test(struct kunit *test)
|
|||
free_mock_sb(mock_sb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that hfsplus_hash_dentry() hashes two names identically when they
|
||||
* differ only in the (non-canonical) typed order of the same combining
|
||||
* marks - both must canonicalize to the same stored form, so they must
|
||||
* hash the same or dcache lookups would spuriously miss.
|
||||
*/
|
||||
static void hfsplus_hash_dentry_combining_reorder_test(struct kunit *test)
|
||||
{
|
||||
struct test_mock_sb *mock_sb;
|
||||
struct qstr str1, str2;
|
||||
int result;
|
||||
|
||||
mock_sb = setup_mock_sb();
|
||||
KUNIT_ASSERT_NOT_NULL(test, mock_sb);
|
||||
|
||||
setup_mock_dentry(&mock_sb->sb);
|
||||
mock_sb->nls.char2uni = test_char2uni_utf8;
|
||||
|
||||
create_qstr(&str1, "e\xcc\x81\xcc\xa3"); /* acute, then dot-below */
|
||||
result = hfsplus_hash_dentry(&test_dentry, &str1);
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
|
||||
create_qstr(&str2, "e\xcc\xa3\xcc\x81"); /* dot-below, then acute */
|
||||
result = hfsplus_hash_dentry(&test_dentry, &str2);
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
|
||||
|
||||
free_mock_sb(mock_sb);
|
||||
}
|
||||
|
||||
/* Test hfsplus_compare_dentry basic functionality */
|
||||
static void hfsplus_compare_dentry_basic_test(struct kunit *test)
|
||||
{
|
||||
|
|
@ -1552,6 +1801,59 @@ static void hfsplus_compare_dentry_combined_flags_test(struct kunit *test)
|
|||
free_mock_sb(mock_sb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that hfsplus_compare_dentry() treats two names as equal when they
|
||||
* differ only in the (non-canonical) typed order of the same combining
|
||||
* marks, since both refer to the same canonically-ordered catalog entry.
|
||||
*/
|
||||
static void hfsplus_compare_dentry_combining_reorder_test(struct kunit *test)
|
||||
{
|
||||
struct test_mock_sb *mock_sb;
|
||||
struct qstr name;
|
||||
int result;
|
||||
|
||||
mock_sb = setup_mock_sb();
|
||||
KUNIT_ASSERT_NOT_NULL(test, mock_sb);
|
||||
|
||||
setup_mock_dentry(&mock_sb->sb);
|
||||
mock_sb->nls.char2uni = test_char2uni_utf8;
|
||||
|
||||
create_qstr(&name, "e\xcc\xa3\xcc\x81"); /* dot-below, then acute */
|
||||
result = hfsplus_compare_dentry(&test_dentry, 5, "e\xcc\x81\xcc\xa3",
|
||||
&name); /* acute, then dot-below */
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
|
||||
free_mock_sb(mock_sb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that a precomposed character using one of fsck_hfs's known legacy
|
||||
* decompositions compares equal to the already-decomposed form of the
|
||||
* same character - i.e. that hfsplus_compare_dentry() applies the same
|
||||
* legacy-decomposition correction as hfsplus_asc2uni() does on storage,
|
||||
* so a lookup finds the entry regardless of which form was typed.
|
||||
*/
|
||||
static void hfsplus_compare_dentry_legacy_decomp_test(struct kunit *test)
|
||||
{
|
||||
struct test_mock_sb *mock_sb;
|
||||
struct qstr name;
|
||||
int result;
|
||||
|
||||
mock_sb = setup_mock_sb();
|
||||
KUNIT_ASSERT_NOT_NULL(test, mock_sb);
|
||||
|
||||
setup_mock_dentry(&mock_sb->sb);
|
||||
mock_sb->nls.char2uni = test_char2uni_utf8;
|
||||
|
||||
/* "N" + COMBINING GRAVE ACCENT (U+0300), already decomposed */
|
||||
create_qstr(&name, "N\xcc\x80");
|
||||
/* U+01F8, precomposed */
|
||||
result = hfsplus_compare_dentry(&test_dentry, 2, "\xc7\xb8", &name);
|
||||
KUNIT_EXPECT_EQ(test, 0, result);
|
||||
|
||||
free_mock_sb(mock_sb);
|
||||
}
|
||||
|
||||
static struct kunit_case hfsplus_unicode_test_cases[] = {
|
||||
KUNIT_CASE(hfsplus_strcasecmp_test),
|
||||
KUNIT_CASE(hfsplus_strcmp_test),
|
||||
|
|
@ -1567,12 +1869,17 @@ static struct kunit_case hfsplus_unicode_test_cases[] = {
|
|||
KUNIT_CASE(hfsplus_asc2uni_buffer_limits_test),
|
||||
KUNIT_CASE(hfsplus_asc2uni_edge_cases_test),
|
||||
KUNIT_CASE(hfsplus_asc2uni_decompose_test),
|
||||
KUNIT_CASE(hfsplus_asc2uni_combining_reorder_test),
|
||||
KUNIT_CASE(hfsplus_unicode_combining_reorder_roundtrip_test),
|
||||
KUNIT_CASE(hfsplus_asc2uni_legacy_decomp_test),
|
||||
KUNIT_CASE(hfsplus_asc2uni_legacy_seq_fixup_test),
|
||||
KUNIT_CASE(hfsplus_hash_dentry_basic_test),
|
||||
KUNIT_CASE(hfsplus_hash_dentry_casefold_test),
|
||||
KUNIT_CASE(hfsplus_hash_dentry_special_chars_test),
|
||||
KUNIT_CASE(hfsplus_hash_dentry_decompose_test),
|
||||
KUNIT_CASE(hfsplus_hash_dentry_consistency_test),
|
||||
KUNIT_CASE(hfsplus_hash_dentry_edge_cases_test),
|
||||
KUNIT_CASE(hfsplus_hash_dentry_combining_reorder_test),
|
||||
KUNIT_CASE(hfsplus_compare_dentry_basic_test),
|
||||
KUNIT_CASE(hfsplus_compare_dentry_casefold_test),
|
||||
KUNIT_CASE(hfsplus_compare_dentry_special_chars_test),
|
||||
|
|
@ -1580,6 +1887,8 @@ static struct kunit_case hfsplus_unicode_test_cases[] = {
|
|||
KUNIT_CASE(hfsplus_compare_dentry_decompose_test),
|
||||
KUNIT_CASE(hfsplus_compare_dentry_edge_cases_test),
|
||||
KUNIT_CASE(hfsplus_compare_dentry_combined_flags_test),
|
||||
KUNIT_CASE(hfsplus_compare_dentry_combining_reorder_test),
|
||||
KUNIT_CASE(hfsplus_compare_dentry_legacy_decomp_test),
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user