| 1 | |
| 2 | |
| 3 | * linux/fs/hfsplus/unicode.c
|
| 4 | *
|
| 5 | * Copyright (C) 2001
|
| 6 | * Brad Boyer ([email protected])
|
| 7 | * (C) 2003 Ardis Technologies <[email protected]>
|
| 8 | *
|
| 9 | * Handler routines for unicode strings
|
| 10 |
|
| 11 |
|
| 12 | #include <linux/types.h>
|
| 13 | #include <linux/nls.h>
|
| 14 |
|
| 15 | #include <kunit/visibility.h>
|
| 16 |
|
| 17 | #include "hfsplus_fs.h"
|
| 18 | #include "hfsplus_raw.h"
|
| 19 | #include "unicode.h"
|
| 20 |
|
| 21 |
|
| 22 |
|
| 23 | static inline u16 case_fold(u16 c)
|
| 24 | {
|
| 25 | u16 tmp;
|
| 26 |
|
| 27 | tmp = hfsplus_case_fold_table[c >> 8];
|
| 28 | if (tmp)
|
| 29 | tmp = hfsplus_case_fold_table[tmp + (c & 0xff)];
|
| 30 | else
|
| 31 | tmp = c;
|
| 32 | return tmp;
|
| 33 | }
|
| 34 |
|
| 35 |
|
| 36 | int hfsplus_strcasecmp(const struct hfsplus_unistr *s1,
|
| 37 | const struct hfsplus_unistr *s2)
|
| 38 | {
|
| 39 | u16 len1, len2, c1, c2;
|
| 40 | const hfsplus_unichr *p1, *p2;
|
| 41 |
|
| 42 | len1 = be16_to_cpu(s1->length);
|
| 43 | len2 = be16_to_cpu(s2->length);
|
| 44 | p1 = s1->unicode;
|
| 45 | p2 = s2->unicode;
|
| 46 |
|
| 47 | if (len1 > HFSPLUS_MAX_STRLEN) {
|
| 48 | len1 = HFSPLUS_MAX_STRLEN;
|
| 49 | pr_err("invalid length %u has been corrected to %d\n",
|
| 50 | be16_to_cpu(s1->length), len1);
|
| 51 | }
|
| 52 |
|
| 53 | if (len2 > HFSPLUS_MAX_STRLEN) {
|
| 54 | len2 = HFSPLUS_MAX_STRLEN;
|
| 55 | pr_err("invalid length %u has been corrected to %d\n",
|
| 56 | be16_to_cpu(s2->length), len2);
|
| 57 | }
|
| 58 |
|
| 59 | while (1) {
|
| 60 | c1 = c2 = 0;
|
| 61 |
|
| 62 | while (len1 && !c1) {
|
| 63 | c1 = case_fold(be16_to_cpu(*p1));
|
| 64 | p1++;
|
| 65 | len1--;
|
| 66 | }
|
| 67 | while (len2 && !c2) {
|
| 68 | c2 = case_fold(be16_to_cpu(*p2));
|
| 69 | p2++;
|
| 70 | len2--;
|
| 71 | }
|
| 72 |
|
| 73 | if (c1 != c2)
|
| 74 | return (c1 < c2) ? -1 : 1;
|
| 75 | if (!c1 && !c2)
|
| 76 | return 0;
|
| 77 | }
|
| 78 | }
|
| 79 | EXPORT_SYMBOL_IF_KUNIT(hfsplus_strcasecmp);
|
| 80 |
|
| 81 |
|
| 82 | int hfsplus_strcmp(const struct hfsplus_unistr *s1,
|
| 83 | const struct hfsplus_unistr *s2)
|
| 84 | {
|
| 85 | u16 len1, len2, c1, c2;
|
| 86 | const hfsplus_unichr *p1, *p2;
|
| 87 | int len;
|
| 88 |
|
| 89 | len1 = be16_to_cpu(s1->length);
|
| 90 | len2 = be16_to_cpu(s2->length);
|
| 91 | p1 = s1->unicode;
|
| 92 | p2 = s2->unicode;
|
| 93 |
|
| 94 | if (len1 > HFSPLUS_MAX_STRLEN) {
|
| 95 | len1 = HFSPLUS_MAX_STRLEN;
|
| 96 | pr_err("invalid length %u has been corrected to %d\n",
|
| 97 | be16_to_cpu(s1->length), len1);
|
| 98 | }
|
| 99 |
|
| 100 | if (len2 > HFSPLUS_MAX_STRLEN) {
|
| 101 | len2 = HFSPLUS_MAX_STRLEN;
|
| 102 | pr_err("invalid length %u has been corrected to %d\n",
|
| 103 | be16_to_cpu(s2->length), len2);
|
| 104 | }
|
| 105 |
|
| 106 | for (len = min(len1, len2); len > 0; len--) {
|
| 107 | c1 = be16_to_cpu(*p1);
|
| 108 | c2 = be16_to_cpu(*p2);
|
| 109 | if (c1 != c2)
|
| 110 | return c1 < c2 ? -1 : 1;
|
| 111 | p1++;
|
| 112 | p2++;
|
| 113 | }
|
| 114 |
|
| 115 | return len1 < len2 ? -1 :
|
| 116 | len1 > len2 ? 1 : 0;
|
| 117 | }
|
| 118 | EXPORT_SYMBOL_IF_KUNIT(hfsplus_strcmp);
|
| 119 |
|
| 120 | #define Hangul_SBase 0xac00
|
| 121 | #define Hangul_LBase 0x1100
|
| 122 | #define Hangul_VBase 0x1161
|
| 123 | #define Hangul_TBase 0x11a7
|
| 124 | #define Hangul_SCount 11172
|
| 125 | #define Hangul_LCount 19
|
| 126 | #define Hangul_VCount 21
|
| 127 | #define Hangul_TCount 28
|
| 128 | #define Hangul_NCount (Hangul_VCount * Hangul_TCount)
|
| 129 |
|
| 130 |
|
| 131 | static u16 *hfsplus_compose_lookup(u16 *p, u16 cc)
|
| 132 | {
|
| 133 | int i, s, e;
|
| 134 |
|
| 135 | s = 1;
|
| 136 | e = p[1];
|
| 137 | if (!e || cc < p[s * 2] || cc > p[e * 2])
|
| 138 | return NULL;
|
| 139 | do {
|
| 140 | i = (s + e) / 2;
|
| 141 | if (cc > p[i * 2])
|
| 142 | s = i + 1;
|
| 143 | else if (cc < p[i * 2])
|
| 144 | e = i - 1;
|
| 145 | else
|
| 146 | return hfsplus_compose_table + p[i * 2 + 1];
|
| 147 | } while (s <= e);
|
| 148 | return NULL;
|
| 149 | }
|
| 150 |
|
| 151 | |
| 152 | * In HFS+, a filename can contain / because : is the separator.
|
| 153 | * The slash is a valid filename character on macOS.
|
| 154 | * But on Linux, / is the path separator and
|
| 155 | * it cannot appear in a filename component.
|
| 156 | * There's a parallel mapping for the NUL character (0 -> U+2400).
|
| 157 | * NUL terminates strings in C/POSIX but is valid in HFS+ filenames.
|
| 158 |
|
| 159 | static inline
|
| 160 | void hfsplus_mac2linux_compatibility_check(u16 symbol, u16 *conversion,
|
| 161 | int name_type)
|
| 162 | {
|
| 163 | *conversion = symbol;
|
| 164 |
|
| 165 | switch (name_type) {
|
| 166 | case HFS_XATTR_NAME:
|
| 167 |
|
| 168 | return;
|
| 169 |
|
| 170 | default:
|
| 171 |
|
| 172 | break;
|
| 173 | }
|
| 174 |
|
| 175 | switch (symbol) {
|
| 176 | case 0:
|
| 177 | *conversion = 0x2400;
|
| 178 | break;
|
| 179 | case '/':
|
| 180 | *conversion = ':';
|
| 181 | break;
|
| 182 | }
|
| 183 | }
|
| 184 |
|
| 185 | static int hfsplus_uni2asc(struct super_block *sb,
|
| 186 | const struct hfsplus_unistr *ustr,
|
| 187 | int max_len, char *astr, int *len_p,
|
| 188 | int name_type)
|
| 189 | {
|
| 190 | const hfsplus_unichr *ip;
|
| 191 | struct nls_table *nls = HFSPLUS_SB(sb)->nls;
|
| 192 | u8 *op;
|
| 193 | u16 cc, c0, c1;
|
| 194 | u16 *ce1, *ce2;
|
| 195 | int i, len, ustrlen, res, compose;
|
| 196 |
|
| 197 | op = astr;
|
| 198 | ip = ustr->unicode;
|
| 199 |
|
| 200 | ustrlen = be16_to_cpu(ustr->length);
|
| 201 | if (ustrlen > max_len) {
|
| 202 | ustrlen = max_len;
|
| 203 | pr_err("invalid length %u has been corrected to %d\n",
|
| 204 | be16_to_cpu(ustr->length), ustrlen);
|
| 205 | }
|
| 206 |
|
| 207 | len = *len_p;
|
| 208 | ce1 = NULL;
|
| 209 | compose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
| 210 |
|
| 211 | while (ustrlen > 0) {
|
| 212 | c0 = be16_to_cpu(*ip++);
|
| 213 | ustrlen--;
|
| 214 |
|
| 215 | if (likely(compose))
|
| 216 | ce1 = hfsplus_compose_lookup(hfsplus_compose_table, c0);
|
| 217 | if (ce1)
|
| 218 | cc = ce1[0];
|
| 219 | else
|
| 220 | cc = 0;
|
| 221 | if (cc) {
|
| 222 |
|
| 223 | if (cc != 0xffff)
|
| 224 | goto done;
|
| 225 | if (!ustrlen)
|
| 226 | goto same;
|
| 227 | c1 = be16_to_cpu(*ip) - Hangul_VBase;
|
| 228 | if (c1 < Hangul_VCount) {
|
| 229 |
|
| 230 | cc = (c0 - Hangul_LBase) * Hangul_VCount;
|
| 231 | cc = (cc + c1) * Hangul_TCount;
|
| 232 | cc += Hangul_SBase;
|
| 233 | ip++;
|
| 234 | ustrlen--;
|
| 235 | if (!ustrlen)
|
| 236 | goto done;
|
| 237 | c1 = be16_to_cpu(*ip) - Hangul_TBase;
|
| 238 | if (c1 > 0 && c1 < Hangul_TCount) {
|
| 239 | cc += c1;
|
| 240 | ip++;
|
| 241 | ustrlen--;
|
| 242 | }
|
| 243 | goto done;
|
| 244 | }
|
| 245 | }
|
| 246 | while (1) {
|
| 247 |
|
| 248 | if (!ustrlen)
|
| 249 | goto same;
|
| 250 | c1 = be16_to_cpu(*ip);
|
| 251 | if (likely(compose))
|
| 252 | ce1 = hfsplus_compose_lookup(
|
| 253 | hfsplus_compose_table, c1);
|
| 254 | if (ce1)
|
| 255 | break;
|
| 256 | hfsplus_mac2linux_compatibility_check(c0, &c0,
|
| 257 | name_type);
|
| 258 | res = nls->uni2char(c0, op, len);
|
| 259 | if (res < 0) {
|
| 260 | if (res == -ENAMETOOLONG)
|
| 261 | goto out;
|
| 262 | *op = '?';
|
| 263 | res = 1;
|
| 264 | }
|
| 265 | op += res;
|
| 266 | len -= res;
|
| 267 | c0 = c1;
|
| 268 | ip++;
|
| 269 | ustrlen--;
|
| 270 | }
|
| 271 | ce2 = hfsplus_compose_lookup(ce1, c0);
|
| 272 | if (ce2) {
|
| 273 | i = 1;
|
| 274 | while (i < ustrlen) {
|
| 275 | ce1 = hfsplus_compose_lookup(ce2,
|
| 276 | be16_to_cpu(ip[i]));
|
| 277 | if (!ce1)
|
| 278 | break;
|
| 279 | i++;
|
| 280 | ce2 = ce1;
|
| 281 | }
|
| 282 | cc = ce2[0];
|
| 283 | if (cc) {
|
| 284 | ip += i;
|
| 285 | ustrlen -= i;
|
| 286 | goto done;
|
| 287 | }
|
| 288 | }
|
| 289 | same:
|
| 290 | hfsplus_mac2linux_compatibility_check(c0, &cc,
|
| 291 | name_type);
|
| 292 | done:
|
| 293 | res = nls->uni2char(cc, op, len);
|
| 294 | if (res < 0) {
|
| 295 | if (res == -ENAMETOOLONG)
|
| 296 | goto out;
|
| 297 | *op = '?';
|
| 298 | res = 1;
|
| 299 | }
|
| 300 | op += res;
|
| 301 | len -= res;
|
| 302 | }
|
| 303 | res = 0;
|
| 304 | out:
|
| 305 | *len_p = (char *)op - astr;
|
| 306 | return res;
|
| 307 | }
|
| 308 |
|
| 309 | inline int hfsplus_uni2asc_str(struct super_block *sb,
|
| 310 | const struct hfsplus_unistr *ustr, char *astr,
|
| 311 | int *len_p)
|
| 312 | {
|
| 313 | return hfsplus_uni2asc(sb,
|
| 314 | ustr, HFSPLUS_MAX_STRLEN,
|
| 315 | astr, len_p,
|
| 316 | HFS_REGULAR_NAME);
|
| 317 | }
|
| 318 | EXPORT_SYMBOL_IF_KUNIT(hfsplus_uni2asc_str);
|
| 319 |
|
| 320 | inline int hfsplus_uni2asc_xattr_str(struct super_block *sb,
|
| 321 | const struct hfsplus_attr_unistr *ustr,
|
| 322 | char *astr, int *len_p)
|
| 323 | {
|
| 324 | return hfsplus_uni2asc(sb, (const struct hfsplus_unistr *)ustr,
|
| 325 | HFSPLUS_ATTR_MAX_STRLEN, astr, len_p,
|
| 326 | HFS_XATTR_NAME);
|
| 327 | }
|
| 328 | EXPORT_SYMBOL_IF_KUNIT(hfsplus_uni2asc_xattr_str);
|
| 329 |
|
| 330 | |
| 331 | * In HFS+, a filename can contain / because : is the separator.
|
| 332 | * The slash is a valid filename character on macOS.
|
| 333 | * But on Linux, / is the path separator and
|
| 334 | * it cannot appear in a filename component.
|
| 335 | * There's a parallel mapping for the NUL character (0 -> U+2400).
|
| 336 | * NUL terminates strings in C/POSIX but is valid in HFS+ filenames.
|
| 337 |
|
| 338 | static inline
|
| 339 | void hfsplus_linux2mac_compatibility_check(wchar_t *uc, int name_type)
|
| 340 | {
|
| 341 | switch (name_type) {
|
| 342 | case HFS_XATTR_NAME:
|
| 343 |
|
| 344 | return;
|
| 345 |
|
| 346 | default:
|
| 347 |
|
| 348 | break;
|
| 349 | }
|
| 350 |
|
| 351 | switch (*uc) {
|
| 352 | case 0x2400:
|
| 353 | *uc = 0;
|
| 354 | break;
|
| 355 | case ':':
|
| 356 | *uc = '/';
|
| 357 | break;
|
| 358 | }
|
| 359 | }
|
| 360 |
|
| 361 | |
| 362 | * Convert one or more ASCII characters into a single unicode character.
|
| 363 | * Returns the number of ASCII characters corresponding to the unicode char.
|
| 364 |
|
| 365 | static inline int asc2unichar(struct super_block *sb, const char *astr, int len,
|
| 366 | wchar_t *uc, int name_type)
|
| 367 | {
|
| 368 | int size = HFSPLUS_SB(sb)->nls->char2uni(astr, len, uc);
|
| 369 |
|
| 370 | if (size <= 0) {
|
| 371 | *uc = '?';
|
| 372 | size = 1;
|
| 373 | }
|
| 374 |
|
| 375 | hfsplus_linux2mac_compatibility_check(uc, name_type);
|
| 376 | return size;
|
| 377 | }
|
| 378 |
|
| 379 |
|
| 380 | static u16 *hfsplus_decompose_nonhangul(wchar_t uc, int *size)
|
| 381 | {
|
| 382 | int off;
|
| 383 |
|
| 384 | off = hfsplus_decompose_table[(uc >> 12) & 0xf];
|
| 385 | if (off == 0 || off == 0xffff)
|
| 386 | return NULL;
|
| 387 |
|
| 388 | off = hfsplus_decompose_table[off + ((uc >> 8) & 0xf)];
|
| 389 | if (!off)
|
| 390 | return NULL;
|
| 391 |
|
| 392 | off = hfsplus_decompose_table[off + ((uc >> 4) & 0xf)];
|
| 393 | if (!off)
|
| 394 | return NULL;
|
| 395 |
|
| 396 | off = hfsplus_decompose_table[off + (uc & 0xf)];
|
| 397 | *size = off & 3;
|
| 398 | if (*size == 0)
|
| 399 | return NULL;
|
| 400 | return hfsplus_decompose_table + (off / 4);
|
| 401 | }
|
| 402 |
|
| 403 | |
| 404 | * Try to decompose a unicode character as Hangul. Return 0 if @uc is not
|
| 405 | * precomposed Hangul, otherwise return the length of the decomposition.
|
| 406 | *
|
| 407 | * This function was adapted from sample code from the Unicode Standard
|
| 408 | * Annex #15: Unicode Normalization Forms, version 3.2.0.
|
| 409 | *
|
| 410 | * Copyright (C) 1991-2018 Unicode, Inc. All rights reserved. Distributed
|
| 411 | * under the Terms of Use in http://www.unicode.org/copyright.html.
|
| 412 |
|
| 413 | static int hfsplus_try_decompose_hangul(wchar_t uc, u16 *result)
|
| 414 | {
|
| 415 | int index;
|
| 416 | int l, v, t;
|
| 417 |
|
| 418 | index = uc - Hangul_SBase;
|
| 419 | if (index < 0 || index >= Hangul_SCount)
|
| 420 | return 0;
|
| 421 |
|
| 422 | l = Hangul_LBase + index / Hangul_NCount;
|
| 423 | v = Hangul_VBase + (index % Hangul_NCount) / Hangul_TCount;
|
| 424 | t = Hangul_TBase + index % Hangul_TCount;
|
| 425 |
|
| 426 | result[0] = l;
|
| 427 | result[1] = v;
|
| 428 | if (t != Hangul_TBase) {
|
| 429 | result[2] = t;
|
| 430 | return 3;
|
| 431 | }
|
| 432 | return 2;
|
| 433 | }
|
| 434 |
|
| 435 |
|
| 436 | static u16 *decompose_unichar(wchar_t uc, int *size, u16 *hangul_buffer)
|
| 437 | {
|
| 438 | u16 *result;
|
| 439 |
|
| 440 |
|
| 441 | result = hangul_buffer;
|
| 442 | *size = hfsplus_try_decompose_hangul(uc, result);
|
| 443 | if (*size == 0)
|
| 444 | result = hfsplus_decompose_nonhangul(uc, size);
|
| 445 | if (!result) {
|
| 446 | |
| 447 | * Not every character with a canonical decomposition is in
|
| 448 | * Apple Technote #1150's own table above; a small, fixed
|
| 449 | * set was only added to the decomposition standard (or had
|
| 450 | * its decomposition corrected) after that table was
|
| 451 | * generated. hfsplus_legacy_decompose() covers those.
|
| 452 |
|
| 453 | const u16 *legacy = hfsplus_legacy_decompose(uc, size);
|
| 454 |
|
| 455 | if (legacy)
|
| 456 | result = memcpy(hangul_buffer, legacy,
|
| 457 | *size * sizeof(*legacy));
|
| 458 | }
|
| 459 | return result;
|
| 460 | }
|
| 461 |
|
| 462 | |
| 463 | * Apply the Unicode Canonical Ordering Algorithm to a decomposed name held
|
| 464 | * as plain host-order code units: within each maximal run of characters
|
| 465 | * that have a nonzero combining class, stable-sort the run into ascending
|
| 466 | * combining-class order. A character with combining class 0 always starts
|
| 467 | * a new run and is never itself reordered.
|
| 468 | *
|
| 469 | * hfsplus_decompose_table decomposes each source character on its own; it
|
| 470 | * says nothing about how the decompositions of two different source
|
| 471 | * characters should be ordered relative to each other when both produce
|
| 472 | * combining marks that end up adjacent. Without this pass, such a
|
| 473 | * sequence can be stored in an order that macOS's own fsck_hfs
|
| 474 | * (FixDecomps() in CatalogCheck.c) considers illegal, even though every
|
| 475 | * individual character was decomposed correctly.
|
| 476 |
|
| 477 | static void hfsplus_canonical_reorder(u16 *ustr, int len)
|
| 478 | {
|
| 479 | int i;
|
| 480 |
|
| 481 | for (i = 1; i < len; i++) {
|
| 482 | u8 cls = hfsplus_combining_class(ustr[i]);
|
| 483 | int j = i;
|
| 484 |
|
| 485 | if (!cls)
|
| 486 | continue;
|
| 487 |
|
| 488 | while (j > 0) {
|
| 489 | u8 prev_cls = hfsplus_combining_class(ustr[j - 1]);
|
| 490 | u16 tmp;
|
| 491 |
|
| 492 | if (!prev_cls || prev_cls <= cls)
|
| 493 | break;
|
| 494 |
|
| 495 | tmp = ustr[j];
|
| 496 | ustr[j] = ustr[j - 1];
|
| 497 | ustr[j - 1] = tmp;
|
| 498 | j--;
|
| 499 | }
|
| 500 | }
|
| 501 | }
|
| 502 |
|
| 503 | #define HFSPLUS_HANGUL_MAX_JAMO (3)
|
| 504 |
|
| 505 | static_assert(HFSPLUS_HANGUL_MAX_JAMO >= HFSPLUS_LEGACY_DECOMP_MAX_LEN,
|
| 506 | "hangul_buffer must fit the longest legacy decomposition too");
|
| 507 |
|
| 508 | |
| 509 | * Decompose and canonically reorder an entire Linux name, as plain
|
| 510 | * host-order code units. hfsplus_asc2uni(), hfsplus_hash_dentry() and
|
| 511 | * hfsplus_compare_dentry() all go through this so that storage, hashing
|
| 512 | * and comparison always agree on what a given name canonicalizes to.
|
| 513 | *
|
| 514 | * @out must hold at least @max_len entries, which must not exceed
|
| 515 | * HFSPLUS_MAX_STRLEN. Returns the number of code units written. If
|
| 516 | * @consumed is non-NULL, it is set to the number of input bytes actually
|
| 517 | * consumed, which is less than @len when @out fills up first.
|
| 518 |
|
| 519 | static int hfsplus_decompose_str(struct super_block *sb, const char *astr,
|
| 520 | int len, int max_len, int name_type,
|
| 521 | u16 *out, int *consumed)
|
| 522 | {
|
| 523 | int decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
| 524 | const char *start = astr;
|
| 525 | int outlen = 0;
|
| 526 |
|
| 527 | while (outlen < max_len && len > 0) {
|
| 528 | u16 *dstr;
|
| 529 | u16 dhangul[HFSPLUS_HANGUL_MAX_JAMO];
|
| 530 | int dsize, size;
|
| 531 | wchar_t c;
|
| 532 |
|
| 533 | size = asc2unichar(sb, astr, len, &c, name_type);
|
| 534 |
|
| 535 | dstr = decompose ? decompose_unichar(c, &dsize, dhangul) : NULL;
|
| 536 | if (dstr) {
|
| 537 | if (outlen + dsize > max_len)
|
| 538 | break;
|
| 539 | do {
|
| 540 | out[outlen++] = *dstr++;
|
| 541 | } while (--dsize > 0);
|
| 542 | } else {
|
| 543 | out[outlen++] = c;
|
| 544 | }
|
| 545 |
|
| 546 | astr += size;
|
| 547 | len -= size;
|
| 548 | }
|
| 549 |
|
| 550 | hfsplus_canonical_reorder(out, outlen);
|
| 551 | hfsplus_fixup_legacy_sequences(out, &outlen);
|
| 552 | if (consumed)
|
| 553 | *consumed = astr - start;
|
| 554 | return outlen;
|
| 555 | }
|
| 556 |
|
| 557 | int hfsplus_asc2uni(struct super_block *sb,
|
| 558 | struct hfsplus_unistr *ustr, int max_unistr_len,
|
| 559 | const char *astr, int len, int name_type)
|
| 560 | {
|
| 561 | u16 buf[HFSPLUS_MAX_STRLEN];
|
| 562 | int outlen, i, consumed;
|
| 563 |
|
| 564 | if (max_unistr_len > HFSPLUS_MAX_STRLEN)
|
| 565 | max_unistr_len = HFSPLUS_MAX_STRLEN;
|
| 566 |
|
| 567 | outlen = hfsplus_decompose_str(sb, astr, len, max_unistr_len,
|
| 568 | name_type, buf, &consumed);
|
| 569 | for (i = 0; i < outlen; i++)
|
| 570 | ustr->unicode[i] = cpu_to_be16(buf[i]);
|
| 571 | ustr->length = cpu_to_be16(outlen);
|
| 572 |
|
| 573 | if (consumed < len)
|
| 574 | return -ENAMETOOLONG;
|
| 575 | return 0;
|
| 576 | }
|
| 577 | EXPORT_SYMBOL_IF_KUNIT(hfsplus_asc2uni);
|
| 578 |
|
| 579 | |
| 580 | * Maximum length of a single maximal run of nonzero-combining-class code
|
| 581 | * units that hfsplus_decompose_iter_next() below will canonically
|
| 582 | * reorder. Every code unit with combining class 0 starts a new run, so
|
| 583 | * this only bounds how many *consecutive* combining marks between two
|
| 584 | * base characters get sorted - real text, and even deliberately
|
| 585 | * adversarial "Zalgo" text, essentially never approaches this. Keeping
|
| 586 | * it small means hfsplus_hash_dentry() and hfsplus_compare_dentry() only
|
| 587 | * ever need a tiny amount of lookahead state, rather than buffering an
|
| 588 | * entire (up to 255-unit) name.
|
| 589 |
|
| 590 | #define HFSPLUS_CCC_RUN_MAX 32
|
| 591 |
|
| 592 | |
| 593 | * Iterator that produces the canonically-ordered, decomposed code units
|
| 594 | * of a Linux name one at a time, without materializing the whole name.
|
| 595 | * hfsplus_hash_dentry() and hfsplus_compare_dentry() use this so that
|
| 596 | * hashing and comparison always agree with what hfsplus_asc2uni() would
|
| 597 | * actually store, while still comparing lazily (stopping at the first
|
| 598 | * difference) the way this code did before canonical reordering existed.
|
| 599 |
|
| 600 | struct hfsplus_decompose_iter {
|
| 601 | struct super_block *sb;
|
| 602 | const char *astr;
|
| 603 | int len;
|
| 604 | int name_type;
|
| 605 | int decompose;
|
| 606 |
|
| 607 | u16 run[HFSPLUS_CCC_RUN_MAX];
|
| 608 | int run_len;
|
| 609 | int run_pos;
|
| 610 | };
|
| 611 |
|
| 612 | static void hfsplus_decompose_iter_init(struct hfsplus_decompose_iter *it,
|
| 613 | struct super_block *sb,
|
| 614 | const char *astr, int len,
|
| 615 | int name_type)
|
| 616 | {
|
| 617 | it->sb = sb;
|
| 618 | it->astr = astr;
|
| 619 | it->len = len;
|
| 620 | it->name_type = name_type;
|
| 621 | it->decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
|
| 622 | it->run_len = 0;
|
| 623 | it->run_pos = 0;
|
| 624 | }
|
| 625 |
|
| 626 | |
| 627 | * Gather the next maximal run of nonzero-combining-class code units
|
| 628 | * (starting with whatever character comes next, base or not) and
|
| 629 | * canonically reorder just that run. A character is only included once
|
| 630 | * we know the class of the first unit it produces, so decoding it is
|
| 631 | * speculative until that's decided.
|
| 632 |
|
| 633 | static bool hfsplus_decompose_iter_refill(struct hfsplus_decompose_iter *it)
|
| 634 | {
|
| 635 | it->run_pos = 0;
|
| 636 | it->run_len = 0;
|
| 637 |
|
| 638 | while (it->len > 0) {
|
| 639 | u16 *dstr;
|
| 640 | u16 dhangul[HFSPLUS_HANGUL_MAX_JAMO];
|
| 641 | int dsize, size;
|
| 642 | wchar_t c;
|
| 643 |
|
| 644 | size = asc2unichar(it->sb, it->astr, it->len, &c,
|
| 645 | it->name_type);
|
| 646 |
|
| 647 | dstr = it->decompose ?
|
| 648 | decompose_unichar(c, &dsize, dhangul) : NULL;
|
| 649 | if (!dstr) {
|
| 650 | dhangul[0] = c;
|
| 651 | dstr = dhangul;
|
| 652 | dsize = 1;
|
| 653 | }
|
| 654 |
|
| 655 | if (it->run_len > 0 && !hfsplus_combining_class(dstr[0]))
|
| 656 | break;
|
| 657 |
|
| 658 | if (it->run_len + dsize > HFSPLUS_CCC_RUN_MAX)
|
| 659 | break;
|
| 660 |
|
| 661 | it->astr += size;
|
| 662 | it->len -= size;
|
| 663 |
|
| 664 | do {
|
| 665 | it->run[it->run_len++] = *dstr++;
|
| 666 | } while (--dsize > 0);
|
| 667 | }
|
| 668 |
|
| 669 | hfsplus_canonical_reorder(it->run, it->run_len);
|
| 670 | hfsplus_fixup_legacy_sequences(it->run, &it->run_len);
|
| 671 | return it->run_len > 0;
|
| 672 | }
|
| 673 |
|
| 674 |
|
| 675 | static int hfsplus_decompose_iter_next(struct hfsplus_decompose_iter *it)
|
| 676 | {
|
| 677 | if (it->run_pos >= it->run_len && !hfsplus_decompose_iter_refill(it))
|
| 678 | return -1;
|
| 679 | return it->run[it->run_pos++];
|
| 680 | }
|
| 681 |
|
| 682 | |
| 683 | * Returns the next code unit that matters for comparison/hashing: folded
|
| 684 | * if @casefold, with any character folding to 0 ("ignorable") skipped
|
| 685 | * entirely. Returns a negative value once @it is exhausted.
|
| 686 |
|
| 687 | static int hfsplus_decompose_iter_next_folded(struct hfsplus_decompose_iter *it,
|
| 688 | int casefold)
|
| 689 | {
|
| 690 | int c;
|
| 691 |
|
| 692 | do {
|
| 693 | c = hfsplus_decompose_iter_next(it);
|
| 694 | if (c < 0)
|
| 695 | return -1;
|
| 696 | if (casefold)
|
| 697 | c = case_fold(c);
|
| 698 | } while (casefold && !c);
|
| 699 |
|
| 700 | return c;
|
| 701 | }
|
| 702 |
|
| 703 | |
| 704 | * Hash a string to an integer as appropriate for the HFS+ filesystem.
|
| 705 | * Composed unicode characters are decomposed and case-folding is performed
|
| 706 | * if the appropriate bits are (un)set on the superblock.
|
| 707 |
|
| 708 | int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str)
|
| 709 | {
|
| 710 | struct super_block *sb = dentry->d_sb;
|
| 711 | int casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
|
| 712 | struct hfsplus_decompose_iter it;
|
| 713 | unsigned long hash;
|
| 714 | int c;
|
| 715 |
|
| 716 | hfsplus_decompose_iter_init(&it, sb, str->name, str->len,
|
| 717 | HFS_REGULAR_NAME);
|
| 718 |
|
| 719 | hash = init_name_hash(dentry);
|
| 720 | while ((c = hfsplus_decompose_iter_next_folded(&it, casefold)) >= 0)
|
| 721 | hash = partial_name_hash(c, hash);
|
| 722 | str->hash = end_name_hash(hash);
|
| 723 |
|
| 724 | return 0;
|
| 725 | }
|
| 726 | EXPORT_SYMBOL_IF_KUNIT(hfsplus_hash_dentry);
|
| 727 |
|
| 728 | |
| 729 | * Compare strings with HFS+ filename ordering.
|
| 730 | * Composed unicode characters are decomposed and case-folding is performed
|
| 731 | * if the appropriate bits are (un)set on the superblock.
|
| 732 |
|
| 733 | int hfsplus_compare_dentry(const struct dentry *dentry,
|
| 734 | unsigned int len, const char *str, const struct qstr *name)
|
| 735 | {
|
| 736 | struct super_block *sb = dentry->d_sb;
|
| 737 | int casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
|
| 738 | struct hfsplus_decompose_iter it1, it2;
|
| 739 |
|
| 740 | hfsplus_decompose_iter_init(&it1, sb, str, len, HFS_REGULAR_NAME);
|
| 741 | hfsplus_decompose_iter_init(&it2, sb, name->name, name->len,
|
| 742 | HFS_REGULAR_NAME);
|
| 743 |
|
| 744 | while (1) {
|
| 745 | int c1 = hfsplus_decompose_iter_next_folded(&it1, casefold);
|
| 746 | int c2 = hfsplus_decompose_iter_next_folded(&it2, casefold);
|
| 747 |
|
| 748 | if (c1 < 0 || c2 < 0)
|
| 749 | return c1 == c2 ? 0 : (c1 < 0 ? -1 : 1);
|
| 750 | if (c1 != c2)
|
| 751 | return c1 < c2 ? -1 : 1;
|
| 752 | }
|
| 753 | }
|
| 754 | EXPORT_SYMBOL_IF_KUNIT(hfsplus_compare_dentry);
|