projects/linux·blob master · refs

1904 lines · 57.1 KiB

1// SPDX-License-Identifier: GPL-2.0
2/*
3 * KUnit tests for HFS+ Unicode string operations
4 *
5 * Copyright (C) 2025 Viacheslav Dubeyko <[email protected]>
6 */
7
8#include <kunit/test.h>
9#include <linux/nls.h>
10#include <linux/dcache.h>
11#include <linux/stringhash.h>
12#include "hfsplus_fs.h"
13#include "unicode.h"
14
15struct test_mock_string_env {
16 struct hfsplus_unistr str1;
17 struct hfsplus_unistr str2;
18 char *buf;
19 u32 buf_size;
20};
21
22static struct test_mock_string_env *setup_mock_str_env(u32 buf_size)
23{
24 struct test_mock_string_env *env;
25
26 env = kzalloc_obj(struct test_mock_string_env);
27 if (!env)
28 return NULL;
29
30 env->buf = kzalloc(buf_size, GFP_KERNEL);
31 if (!env->buf) {
32 kfree(env);
33 return NULL;
34 }
35
36 env->buf_size = buf_size;
37
38 return env;
39}
40
41static void free_mock_str_env(struct test_mock_string_env *env)
42{
43 kfree(env->buf);
44 kfree(env);
45}
46
47/* Helper function to create hfsplus_unistr */
48static void create_unistr(struct hfsplus_unistr *ustr, const char *ascii_str)
49{
50 int len = strlen(ascii_str);
51 int i;
52
53 memset(ustr->unicode, 0, sizeof(ustr->unicode));
54
55 ustr->length = cpu_to_be16(len);
56 for (i = 0; i < len && i < HFSPLUS_MAX_STRLEN; i++)
57 ustr->unicode[i] = cpu_to_be16((u16)ascii_str[i]);
58}
59
60static void corrupt_unistr(struct hfsplus_unistr *ustr)
61{
62 ustr->length = cpu_to_be16(U16_MAX);
63}
64
65/* Test hfsplus_strcasecmp function */
66static void hfsplus_strcasecmp_test(struct kunit *test)
67{
68 struct test_mock_string_env *mock_env;
69
70 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
71 KUNIT_ASSERT_NOT_NULL(test, mock_env);
72
73 /* Test identical strings */
74 create_unistr(&mock_env->str1, "hello");
75 create_unistr(&mock_env->str2, "hello");
76 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
77 &mock_env->str2));
78
79 /* Test case insensitive comparison */
80 create_unistr(&mock_env->str1, "Hello");
81 create_unistr(&mock_env->str2, "hello");
82 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
83 &mock_env->str2));
84
85 create_unistr(&mock_env->str1, "HELLO");
86 create_unistr(&mock_env->str2, "hello");
87 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
88 &mock_env->str2));
89
90 /* Test different strings */
91 create_unistr(&mock_env->str1, "apple");
92 create_unistr(&mock_env->str2, "banana");
93 KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
94 &mock_env->str2), 0);
95
96 create_unistr(&mock_env->str1, "zebra");
97 create_unistr(&mock_env->str2, "apple");
98 KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
99 &mock_env->str2), 0);
100
101 /* Test different lengths */
102 create_unistr(&mock_env->str1, "test");
103 create_unistr(&mock_env->str2, "testing");
104 KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
105 &mock_env->str2), 0);
106
107 create_unistr(&mock_env->str1, "testing");
108 create_unistr(&mock_env->str2, "test");
109 KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
110 &mock_env->str2), 0);
111
112 /* Test empty strings */
113 create_unistr(&mock_env->str1, "");
114 create_unistr(&mock_env->str2, "");
115 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
116 &mock_env->str2));
117
118 create_unistr(&mock_env->str1, "");
119 create_unistr(&mock_env->str2, "test");
120 KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
121 &mock_env->str2), 0);
122
123 /* Test single characters */
124 create_unistr(&mock_env->str1, "A");
125 create_unistr(&mock_env->str2, "a");
126 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
127 &mock_env->str2));
128
129 create_unistr(&mock_env->str1, "A");
130 create_unistr(&mock_env->str2, "B");
131 KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
132 &mock_env->str2), 0);
133
134 /* Test maximum length strings */
135 memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN);
136 mock_env->buf[HFSPLUS_MAX_STRLEN] = '\0';
137 create_unistr(&mock_env->str1, mock_env->buf);
138 create_unistr(&mock_env->str2, mock_env->buf);
139 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
140 &mock_env->str2));
141
142 /* Change one character in the middle */
143 mock_env->buf[HFSPLUS_MAX_STRLEN / 2] = 'b';
144 create_unistr(&mock_env->str2, mock_env->buf);
145 KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
146 &mock_env->str2), 0);
147
148 /* Test corrupted strings */
149 create_unistr(&mock_env->str1, "");
150 corrupt_unistr(&mock_env->str1);
151 create_unistr(&mock_env->str2, "");
152 KUNIT_EXPECT_NE(test, 0, hfsplus_strcasecmp(&mock_env->str1,
153 &mock_env->str2));
154
155 create_unistr(&mock_env->str1, "");
156 create_unistr(&mock_env->str2, "");
157 corrupt_unistr(&mock_env->str2);
158 KUNIT_EXPECT_NE(test, 0, hfsplus_strcasecmp(&mock_env->str1,
159 &mock_env->str2));
160
161 create_unistr(&mock_env->str1, "test");
162 corrupt_unistr(&mock_env->str1);
163 create_unistr(&mock_env->str2, "testing");
164 KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
165 &mock_env->str2), 0);
166
167 create_unistr(&mock_env->str1, "test");
168 create_unistr(&mock_env->str2, "testing");
169 corrupt_unistr(&mock_env->str2);
170 KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
171 &mock_env->str2), 0);
172
173 create_unistr(&mock_env->str1, "testing");
174 corrupt_unistr(&mock_env->str1);
175 create_unistr(&mock_env->str2, "test");
176 KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
177 &mock_env->str2), 0);
178
179 create_unistr(&mock_env->str1, "testing");
180 create_unistr(&mock_env->str2, "test");
181 corrupt_unistr(&mock_env->str2);
182 KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
183 &mock_env->str2), 0);
184
185 free_mock_str_env(mock_env);
186}
187
188/* Test hfsplus_strcmp function (case-sensitive) */
189static void hfsplus_strcmp_test(struct kunit *test)
190{
191 struct test_mock_string_env *mock_env;
192
193 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
194 KUNIT_ASSERT_NOT_NULL(test, mock_env);
195
196 /* Test identical strings */
197 create_unistr(&mock_env->str1, "hello");
198 create_unistr(&mock_env->str2, "hello");
199 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
200 &mock_env->str2));
201
202 /* Test case sensitive comparison - should NOT be equal */
203 create_unistr(&mock_env->str1, "Hello");
204 create_unistr(&mock_env->str2, "hello");
205 KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1,
206 &mock_env->str2));
207 /* 'H' < 'h' in Unicode */
208 KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
209 &mock_env->str2), 0);
210
211 /* Test lexicographic ordering */
212 create_unistr(&mock_env->str1, "apple");
213 create_unistr(&mock_env->str2, "banana");
214 KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
215 &mock_env->str2), 0);
216
217 create_unistr(&mock_env->str1, "zebra");
218 create_unistr(&mock_env->str2, "apple");
219 KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
220 &mock_env->str2), 0);
221
222 /* Test different lengths with common prefix */
223 create_unistr(&mock_env->str1, "test");
224 create_unistr(&mock_env->str2, "testing");
225 KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
226 &mock_env->str2), 0);
227
228 create_unistr(&mock_env->str1, "testing");
229 create_unistr(&mock_env->str2, "test");
230 KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
231 &mock_env->str2), 0);
232
233 /* Test empty strings */
234 create_unistr(&mock_env->str1, "");
235 create_unistr(&mock_env->str2, "");
236 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
237 &mock_env->str2));
238
239 /* Test maximum length strings */
240 memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN);
241 mock_env->buf[HFSPLUS_MAX_STRLEN] = '\0';
242 create_unistr(&mock_env->str1, mock_env->buf);
243 create_unistr(&mock_env->str2, mock_env->buf);
244 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
245 &mock_env->str2));
246
247 /* Change one character in the middle */
248 mock_env->buf[HFSPLUS_MAX_STRLEN / 2] = 'b';
249 create_unistr(&mock_env->str2, mock_env->buf);
250 KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
251 &mock_env->str2), 0);
252
253 /* Test corrupted strings */
254 create_unistr(&mock_env->str1, "");
255 corrupt_unistr(&mock_env->str1);
256 create_unistr(&mock_env->str2, "");
257 KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1,
258 &mock_env->str2));
259
260 create_unistr(&mock_env->str1, "");
261 create_unistr(&mock_env->str2, "");
262 corrupt_unistr(&mock_env->str2);
263 KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1,
264 &mock_env->str2));
265
266 create_unistr(&mock_env->str1, "test");
267 corrupt_unistr(&mock_env->str1);
268 create_unistr(&mock_env->str2, "testing");
269 KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
270 &mock_env->str2), 0);
271
272 create_unistr(&mock_env->str1, "test");
273 create_unistr(&mock_env->str2, "testing");
274 corrupt_unistr(&mock_env->str2);
275 KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
276 &mock_env->str2), 0);
277
278 create_unistr(&mock_env->str1, "testing");
279 corrupt_unistr(&mock_env->str1);
280 create_unistr(&mock_env->str2, "test");
281 KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
282 &mock_env->str2), 0);
283
284 create_unistr(&mock_env->str1, "testing");
285 create_unistr(&mock_env->str2, "test");
286 corrupt_unistr(&mock_env->str2);
287 KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
288 &mock_env->str2), 0);
289
290 free_mock_str_env(mock_env);
291}
292
293/* Test Unicode edge cases */
294static void hfsplus_unicode_edge_cases_test(struct kunit *test)
295{
296 struct test_mock_string_env *mock_env;
297
298 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
299 KUNIT_ASSERT_NOT_NULL(test, mock_env);
300
301 /* Test with special characters */
302 mock_env->str1.length = cpu_to_be16(3);
303 mock_env->str1.unicode[0] = cpu_to_be16(0x00E9); /* é */
304 mock_env->str1.unicode[1] = cpu_to_be16(0x00F1); /* ñ */
305 mock_env->str1.unicode[2] = cpu_to_be16(0x00FC); /* ü */
306
307 mock_env->str2.length = cpu_to_be16(3);
308 mock_env->str2.unicode[0] = cpu_to_be16(0x00E9); /* é */
309 mock_env->str2.unicode[1] = cpu_to_be16(0x00F1); /* ñ */
310 mock_env->str2.unicode[2] = cpu_to_be16(0x00FC); /* ü */
311
312 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
313 &mock_env->str2));
314 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
315 &mock_env->str2));
316
317 /* Test with different special characters */
318 mock_env->str2.unicode[1] = cpu_to_be16(0x00F2); /* ò */
319 KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1,
320 &mock_env->str2));
321
322 /* Test null characters within string (should be handled correctly) */
323 mock_env->str1.length = cpu_to_be16(3);
324 mock_env->str1.unicode[0] = cpu_to_be16('a');
325 mock_env->str1.unicode[1] = cpu_to_be16(0x0000); /* null */
326 mock_env->str1.unicode[2] = cpu_to_be16('b');
327
328 mock_env->str2.length = cpu_to_be16(3);
329 mock_env->str2.unicode[0] = cpu_to_be16('a');
330 mock_env->str2.unicode[1] = cpu_to_be16(0x0000); /* null */
331 mock_env->str2.unicode[2] = cpu_to_be16('b');
332
333 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
334 &mock_env->str2));
335
336 free_mock_str_env(mock_env);
337}
338
339/* Test boundary conditions */
340static void hfsplus_unicode_boundary_test(struct kunit *test)
341{
342 struct test_mock_string_env *mock_env;
343 int i;
344
345 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
346 KUNIT_ASSERT_NOT_NULL(test, mock_env);
347
348 /* Test maximum length boundary */
349 mock_env->str1.length = cpu_to_be16(HFSPLUS_MAX_STRLEN);
350 mock_env->str2.length = cpu_to_be16(HFSPLUS_MAX_STRLEN);
351
352 for (i = 0; i < HFSPLUS_MAX_STRLEN; i++) {
353 mock_env->str1.unicode[i] = cpu_to_be16('A');
354 mock_env->str2.unicode[i] = cpu_to_be16('A');
355 }
356
357 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
358 &mock_env->str2));
359
360 /* Change last character */
361 mock_env->str2.unicode[HFSPLUS_MAX_STRLEN - 1] = cpu_to_be16('B');
362 KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
363 &mock_env->str2), 0);
364
365 /* Test zero length strings */
366 mock_env->str1.length = cpu_to_be16(0);
367 mock_env->str2.length = cpu_to_be16(0);
368 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
369 &mock_env->str2));
370 KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
371 &mock_env->str2));
372
373 /* Test one character vs empty */
374 mock_env->str1.length = cpu_to_be16(1);
375 mock_env->str1.unicode[0] = cpu_to_be16('A');
376 mock_env->str2.length = cpu_to_be16(0);
377 KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
378 &mock_env->str2), 0);
379 KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
380 &mock_env->str2), 0);
381
382 free_mock_str_env(mock_env);
383}
384
385/* Mock superblock and NLS table for testing hfsplus_uni2asc */
386struct test_mock_sb {
387 struct nls_table nls;
388 struct hfsplus_sb_info sb_info;
389 struct super_block sb;
390};
391
392static struct test_mock_sb *setup_mock_sb(void)
393{
394 struct test_mock_sb *ptr;
395
396 ptr = kzalloc_obj(struct test_mock_sb);
397 if (!ptr)
398 return NULL;
399
400 ptr->nls.charset = "utf8";
401 ptr->nls.uni2char = NULL; /* Will use default behavior */
402 ptr->sb_info.nls = &ptr->nls;
403 ptr->sb.s_fs_info = &ptr->sb_info;
404
405 /* Set default flags - no decomposition, no case folding */
406 clear_bit(HFSPLUS_SB_NODECOMPOSE, &ptr->sb_info.flags);
407 clear_bit(HFSPLUS_SB_CASEFOLD, &ptr->sb_info.flags);
408
409 return ptr;
410}
411
412static void free_mock_sb(struct test_mock_sb *ptr)
413{
414 kfree(ptr);
415}
416
417/* Simple uni2char implementation for testing */
418static int test_uni2char(wchar_t uni, unsigned char *out, int boundlen)
419{
420 if (boundlen <= 0)
421 return -ENAMETOOLONG;
422
423 if (uni < 0x80) {
424 *out = (unsigned char)uni;
425 return 1;
426 }
427
428 /* For non-ASCII, just use '?' as fallback */
429 *out = '?';
430 return 1;
431}
432
433/* Test hfsplus_uni2asc basic functionality */
434static void hfsplus_uni2asc_basic_test(struct kunit *test)
435{
436 struct test_mock_sb *mock_sb;
437 struct test_mock_string_env *mock_env;
438 int len, result;
439
440 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
441 KUNIT_ASSERT_NOT_NULL(test, mock_env);
442
443 mock_sb = setup_mock_sb();
444 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
445
446 mock_sb->nls.uni2char = test_uni2char;
447
448 /* Test simple ASCII string conversion */
449 create_unistr(&mock_env->str1, "hello");
450 len = mock_env->buf_size;
451 result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
452 mock_env->buf, &len);
453
454 KUNIT_EXPECT_EQ(test, 0, result);
455 KUNIT_EXPECT_EQ(test, 5, len);
456 KUNIT_EXPECT_STREQ(test, "hello", mock_env->buf);
457
458 /* Test empty string */
459 create_unistr(&mock_env->str1, "");
460 len = mock_env->buf_size;
461 result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
462 mock_env->buf, &len);
463
464 KUNIT_EXPECT_EQ(test, 0, result);
465 KUNIT_EXPECT_EQ(test, 0, len);
466
467 /* Test single character */
468 create_unistr(&mock_env->str1, "A");
469 len = mock_env->buf_size;
470 result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
471 mock_env->buf, &len);
472
473 KUNIT_EXPECT_EQ(test, 0, result);
474 KUNIT_EXPECT_EQ(test, 1, len);
475 KUNIT_EXPECT_EQ(test, 'A', mock_env->buf[0]);
476
477 free_mock_str_env(mock_env);
478 free_mock_sb(mock_sb);
479}
480
481/* Test special character handling */
482static void hfsplus_uni2asc_special_chars_test(struct kunit *test)
483{
484 struct test_mock_sb *mock_sb;
485 struct test_mock_string_env *mock_env;
486 int len, result;
487
488 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
489 KUNIT_ASSERT_NOT_NULL(test, mock_env);
490
491 mock_sb = setup_mock_sb();
492 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
493
494 mock_sb->nls.uni2char = test_uni2char;
495
496 /* Test null character conversion (should become 0x2400) */
497 mock_env->str1.length = cpu_to_be16(1);
498 mock_env->str1.unicode[0] = cpu_to_be16(0x0000);
499 len = mock_env->buf_size;
500 result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
501 mock_env->buf, &len);
502
503 KUNIT_EXPECT_EQ(test, 0, result);
504 KUNIT_EXPECT_EQ(test, 1, len);
505 /* Our test implementation returns '?' for non-ASCII */
506 KUNIT_EXPECT_EQ(test, '?', mock_env->buf[0]);
507
508 /* Test forward slash conversion (should become colon) */
509 mock_env->str1.length = cpu_to_be16(1);
510 mock_env->str1.unicode[0] = cpu_to_be16('/');
511 len = mock_env->buf_size;
512 result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
513 mock_env->buf, &len);
514
515 KUNIT_EXPECT_EQ(test, 0, result);
516 KUNIT_EXPECT_EQ(test, 1, len);
517 KUNIT_EXPECT_EQ(test, ':', mock_env->buf[0]);
518
519 /* Test string with mixed special characters */
520 mock_env->str1.length = cpu_to_be16(3);
521 mock_env->str1.unicode[0] = cpu_to_be16('a');
522 mock_env->str1.unicode[1] = cpu_to_be16('/');
523 mock_env->str1.unicode[2] = cpu_to_be16('b');
524 len = mock_env->buf_size;
525 result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
526 mock_env->buf, &len);
527
528 KUNIT_EXPECT_EQ(test, 0, result);
529 KUNIT_EXPECT_EQ(test, 3, len);
530 KUNIT_EXPECT_EQ(test, 'a', mock_env->buf[0]);
531 KUNIT_EXPECT_EQ(test, ':', mock_env->buf[1]);
532 KUNIT_EXPECT_EQ(test, 'b', mock_env->buf[2]);
533
534 free_mock_str_env(mock_env);
535 free_mock_sb(mock_sb);
536}
537
538/* Test buffer length handling */
539static void hfsplus_uni2asc_buffer_test(struct kunit *test)
540{
541 struct test_mock_sb *mock_sb;
542 struct test_mock_string_env *mock_env;
543 int len, result;
544
545 mock_env = setup_mock_str_env(10);
546 KUNIT_ASSERT_NOT_NULL(test, mock_env);
547
548 mock_sb = setup_mock_sb();
549 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
550
551 mock_sb->nls.uni2char = test_uni2char;
552
553 /* Test insufficient buffer space */
554 create_unistr(&mock_env->str1, "toolongstring");
555 len = 5; /* Buffer too small */
556 result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
557 mock_env->buf, &len);
558
559 KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
560 KUNIT_EXPECT_EQ(test, 5, len); /* Should be set to consumed length */
561
562 /* Test exact buffer size */
563 create_unistr(&mock_env->str1, "exact");
564 len = 5;
565 result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
566 mock_env->buf, &len);
567
568 KUNIT_EXPECT_EQ(test, 0, result);
569 KUNIT_EXPECT_EQ(test, 5, len);
570
571 /* Test zero length buffer */
572 create_unistr(&mock_env->str1, "test");
573 len = 0;
574 result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
575 mock_env->buf, &len);
576
577 KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
578 KUNIT_EXPECT_EQ(test, 0, len);
579
580 free_mock_str_env(mock_env);
581 free_mock_sb(mock_sb);
582}
583
584/* Test corrupted unicode string handling */
585static void hfsplus_uni2asc_corrupted_test(struct kunit *test)
586{
587 struct test_mock_sb *mock_sb;
588 struct test_mock_string_env *mock_env;
589 int len, result;
590
591 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
592 KUNIT_ASSERT_NOT_NULL(test, mock_env);
593
594 mock_sb = setup_mock_sb();
595 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
596
597 mock_sb->nls.uni2char = test_uni2char;
598
599 /* Test corrupted length (too large) */
600 create_unistr(&mock_env->str1, "test");
601 corrupt_unistr(&mock_env->str1); /* Sets length to U16_MAX */
602 len = mock_env->buf_size;
603
604 result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
605 mock_env->buf, &len);
606
607 /* Should still work but with corrected length */
608 KUNIT_EXPECT_EQ(test, 0, result);
609 /*
610 * Length should be corrected to HFSPLUS_MAX_STRLEN
611 * and processed accordingly
612 */
613 KUNIT_EXPECT_GT(test, len, 0);
614
615 free_mock_str_env(mock_env);
616 free_mock_sb(mock_sb);
617}
618
619/* Test edge cases and boundary conditions */
620static void hfsplus_uni2asc_edge_cases_test(struct kunit *test)
621{
622 struct test_mock_sb *mock_sb;
623 struct test_mock_string_env *mock_env;
624 int len, result;
625 int i;
626
627 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN * 2);
628 KUNIT_ASSERT_NOT_NULL(test, mock_env);
629
630 mock_sb = setup_mock_sb();
631 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
632
633 mock_sb->nls.uni2char = test_uni2char;
634
635 /* Test maximum length string */
636 mock_env->str1.length = cpu_to_be16(HFSPLUS_MAX_STRLEN);
637 for (i = 0; i < HFSPLUS_MAX_STRLEN; i++)
638 mock_env->str1.unicode[i] = cpu_to_be16('a');
639
640 len = mock_env->buf_size;
641 result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
642 mock_env->buf, &len);
643
644 KUNIT_EXPECT_EQ(test, 0, result);
645 KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN, len);
646
647 /* Verify all characters are 'a' */
648 for (i = 0; i < HFSPLUS_MAX_STRLEN; i++)
649 KUNIT_EXPECT_EQ(test, 'a', mock_env->buf[i]);
650
651 /* Test string with high Unicode values (non-ASCII) */
652 mock_env->str1.length = cpu_to_be16(3);
653 mock_env->str1.unicode[0] = cpu_to_be16(0x00E9); /* é */
654 mock_env->str1.unicode[1] = cpu_to_be16(0x00F1); /* ñ */
655 mock_env->str1.unicode[2] = cpu_to_be16(0x00FC); /* ü */
656 len = mock_env->buf_size;
657 result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
658 mock_env->buf, &len);
659
660 KUNIT_EXPECT_EQ(test, 0, result);
661 KUNIT_EXPECT_EQ(test, 3, len);
662 /* Our test implementation converts non-ASCII to '?' */
663 KUNIT_EXPECT_EQ(test, '?', mock_env->buf[0]);
664 KUNIT_EXPECT_EQ(test, '?', mock_env->buf[1]);
665 KUNIT_EXPECT_EQ(test, '?', mock_env->buf[2]);
666
667 free_mock_str_env(mock_env);
668 free_mock_sb(mock_sb);
669}
670
671/* Simple char2uni implementation for testing */
672static int test_char2uni(const unsigned char *rawstring,
673 int boundlen, wchar_t *uni)
674{
675 if (boundlen <= 0)
676 return -EINVAL;
677
678 *uni = (wchar_t)*rawstring;
679 return 1;
680}
681
682/* Helper function to check unicode string contents */
683static void check_unistr_content(struct kunit *test,
684 struct hfsplus_unistr *ustr,
685 const char *expected_ascii)
686{
687 int expected_len = strlen(expected_ascii);
688 int actual_len = be16_to_cpu(ustr->length);
689 int i;
690
691 KUNIT_EXPECT_EQ(test, expected_len, actual_len);
692
693 for (i = 0; i < expected_len && i < actual_len; i++) {
694 u16 expected_char = (u16)expected_ascii[i];
695 u16 actual_char = be16_to_cpu(ustr->unicode[i]);
696
697 KUNIT_EXPECT_EQ(test, expected_char, actual_char);
698 }
699}
700
701/* Test hfsplus_asc2uni basic functionality */
702static void hfsplus_asc2uni_basic_test(struct kunit *test)
703{
704 struct test_mock_sb *mock_sb;
705 struct test_mock_string_env *mock_env;
706 int result;
707
708 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
709 KUNIT_ASSERT_NOT_NULL(test, mock_env);
710
711 mock_sb = setup_mock_sb();
712 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
713
714 mock_sb->nls.char2uni = test_char2uni;
715
716 /* Test simple ASCII string conversion */
717 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
718 HFSPLUS_MAX_STRLEN, "hello", 5,
719 HFS_REGULAR_NAME);
720
721 KUNIT_EXPECT_EQ(test, 0, result);
722 check_unistr_content(test, &mock_env->str1, "hello");
723
724 /* Test empty string */
725 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
726 HFSPLUS_MAX_STRLEN, "", 0,
727 HFS_REGULAR_NAME);
728
729 KUNIT_EXPECT_EQ(test, 0, result);
730 KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length));
731
732 /* Test single character */
733 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
734 HFSPLUS_MAX_STRLEN, "A", 1,
735 HFS_REGULAR_NAME);
736
737 KUNIT_EXPECT_EQ(test, 0, result);
738 check_unistr_content(test, &mock_env->str1, "A");
739
740 /* Test null-terminated string with explicit length */
741 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
742 HFSPLUS_MAX_STRLEN, "test\0extra", 4,
743 HFS_REGULAR_NAME);
744
745 KUNIT_EXPECT_EQ(test, 0, result);
746 check_unistr_content(test, &mock_env->str1, "test");
747
748 free_mock_str_env(mock_env);
749 free_mock_sb(mock_sb);
750}
751
752/* Test special character handling in asc2uni */
753static void hfsplus_asc2uni_special_chars_test(struct kunit *test)
754{
755 struct test_mock_sb *mock_sb;
756 struct test_mock_string_env *mock_env;
757 int result;
758
759 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
760 KUNIT_ASSERT_NOT_NULL(test, mock_env);
761
762 mock_sb = setup_mock_sb();
763 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
764
765 mock_sb->nls.char2uni = test_char2uni;
766
767 /* Test colon conversion (should become forward slash) */
768 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
769 HFSPLUS_MAX_STRLEN, ":", 1,
770 HFS_REGULAR_NAME);
771
772 KUNIT_EXPECT_EQ(test, 0, result);
773 KUNIT_EXPECT_EQ(test, 1, be16_to_cpu(mock_env->str1.length));
774 KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[0]));
775
776 /* Test string with mixed special characters */
777 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
778 HFSPLUS_MAX_STRLEN, "a:b", 3,
779 HFS_REGULAR_NAME);
780
781 KUNIT_EXPECT_EQ(test, 0, result);
782 KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(mock_env->str1.length));
783 KUNIT_EXPECT_EQ(test, 'a', be16_to_cpu(mock_env->str1.unicode[0]));
784 KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[1]));
785 KUNIT_EXPECT_EQ(test, 'b', be16_to_cpu(mock_env->str1.unicode[2]));
786
787 /* Test multiple special characters */
788 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
789 HFSPLUS_MAX_STRLEN, ":::", 3,
790 HFS_REGULAR_NAME);
791
792 KUNIT_EXPECT_EQ(test, 0, result);
793 KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(mock_env->str1.length));
794 KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[0]));
795 KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[1]));
796 KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[2]));
797
798 free_mock_str_env(mock_env);
799 free_mock_sb(mock_sb);
800}
801
802/* Test buffer length limits */
803static void hfsplus_asc2uni_buffer_limits_test(struct kunit *test)
804{
805 struct test_mock_sb *mock_sb;
806 struct test_mock_string_env *mock_env;
807 int result;
808
809 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 10);
810 KUNIT_ASSERT_NOT_NULL(test, mock_env);
811
812 mock_sb = setup_mock_sb();
813 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
814
815 mock_sb->nls.char2uni = test_char2uni;
816
817 /* Test exact maximum length */
818 memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN);
819 result = hfsplus_asc2uni(&mock_sb->sb,
820 &mock_env->str1, HFSPLUS_MAX_STRLEN,
821 mock_env->buf, HFSPLUS_MAX_STRLEN,
822 HFS_REGULAR_NAME);
823
824 KUNIT_EXPECT_EQ(test, 0, result);
825 KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN,
826 be16_to_cpu(mock_env->str1.length));
827
828 /* Test exceeding maximum length */
829 memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN + 5);
830 result = hfsplus_asc2uni(&mock_sb->sb,
831 &mock_env->str1, HFSPLUS_MAX_STRLEN,
832 mock_env->buf, HFSPLUS_MAX_STRLEN + 5,
833 HFS_REGULAR_NAME);
834
835 KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
836 KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN,
837 be16_to_cpu(mock_env->str1.length));
838
839 /* Test with smaller max_unistr_len */
840 result = hfsplus_asc2uni(&mock_sb->sb,
841 &mock_env->str1, 5, "toolongstring", 13,
842 HFS_REGULAR_NAME);
843
844 KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
845 KUNIT_EXPECT_EQ(test, 5, be16_to_cpu(mock_env->str1.length));
846
847 /* Test zero max length */
848 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 0, "test", 4,
849 HFS_REGULAR_NAME);
850
851 KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
852 KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length));
853
854 free_mock_str_env(mock_env);
855 free_mock_sb(mock_sb);
856}
857
858/* Test error handling and edge cases */
859static void hfsplus_asc2uni_edge_cases_test(struct kunit *test)
860{
861 struct test_mock_sb *mock_sb;
862 struct hfsplus_unistr ustr;
863 char test_str[] = {'a', '\0', 'b'};
864 int result;
865
866 mock_sb = setup_mock_sb();
867 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
868
869 mock_sb->nls.char2uni = test_char2uni;
870
871 /* Test zero length input */
872 result = hfsplus_asc2uni(&mock_sb->sb,
873 &ustr, HFSPLUS_MAX_STRLEN, "test", 0,
874 HFS_REGULAR_NAME);
875
876 KUNIT_EXPECT_EQ(test, 0, result);
877 KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(ustr.length));
878
879 /* Test input with length mismatch */
880 result = hfsplus_asc2uni(&mock_sb->sb,
881 &ustr, HFSPLUS_MAX_STRLEN, "hello", 3,
882 HFS_REGULAR_NAME);
883
884 KUNIT_EXPECT_EQ(test, 0, result);
885 check_unistr_content(test, &ustr, "hel");
886
887 /* Test with various printable ASCII characters */
888 result = hfsplus_asc2uni(&mock_sb->sb,
889 &ustr, HFSPLUS_MAX_STRLEN, "ABC123!@#", 9,
890 HFS_REGULAR_NAME);
891
892 KUNIT_EXPECT_EQ(test, 0, result);
893 check_unistr_content(test, &ustr, "ABC123!@#");
894
895 /* Test null character in the middle */
896 result = hfsplus_asc2uni(&mock_sb->sb,
897 &ustr, HFSPLUS_MAX_STRLEN, test_str, 3,
898 HFS_REGULAR_NAME);
899
900 KUNIT_EXPECT_EQ(test, 0, result);
901 KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
902 KUNIT_EXPECT_EQ(test, 'a', be16_to_cpu(ustr.unicode[0]));
903 KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(ustr.unicode[1]));
904 KUNIT_EXPECT_EQ(test, 'b', be16_to_cpu(ustr.unicode[2]));
905
906 free_mock_sb(mock_sb);
907}
908
909/* Test decomposition flag behavior */
910static void hfsplus_asc2uni_decompose_test(struct kunit *test)
911{
912 struct test_mock_sb *mock_sb;
913 struct test_mock_string_env *mock_env;
914 int result;
915
916 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
917 KUNIT_ASSERT_NOT_NULL(test, mock_env);
918
919 mock_sb = setup_mock_sb();
920 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
921
922 mock_sb->nls.char2uni = test_char2uni;
923
924 /* Test with decomposition disabled (default) */
925 clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
926 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
927 HFSPLUS_MAX_STRLEN, "test", 4,
928 HFS_REGULAR_NAME);
929
930 KUNIT_EXPECT_EQ(test, 0, result);
931 check_unistr_content(test, &mock_env->str1, "test");
932
933 /* Test with decomposition enabled */
934 set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
935 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str2,
936 HFSPLUS_MAX_STRLEN, "test", 4,
937 HFS_REGULAR_NAME);
938
939 KUNIT_EXPECT_EQ(test, 0, result);
940 check_unistr_content(test, &mock_env->str2, "test");
941
942 /* For simple ASCII, both should produce the same result */
943 KUNIT_EXPECT_EQ(test,
944 be16_to_cpu(mock_env->str1.length),
945 be16_to_cpu(mock_env->str2.length));
946
947 free_mock_str_env(mock_env);
948 free_mock_sb(mock_sb);
949}
950
951/*
952 * Real UTF-8 <-> wchar_t conversion, mirroring what the "utf8" NLS table
953 * actually does (see char2uni()/uni2char() in fs/nls/nls_utf8.c). The
954 * test_char2uni()/test_uni2char() stand-ins above only handle one raw
955 * byte per character, which cannot represent the combining marks (e.g.
956 * U+0301, U+0323) the canonical-reordering tests below need.
957 */
958static int test_char2uni_utf8(const unsigned char *rawstring, int boundlen,
959 wchar_t *uni)
960{
961 unicode_t u;
962 int n = utf8_to_utf32(rawstring, boundlen, &u);
963
964 if (n < 0 || u > MAX_WCHAR_T) {
965 *uni = 0x3f; /* ? */
966 return -EINVAL;
967 }
968 *uni = (wchar_t)u;
969 return n;
970}
971
972static int test_uni2char_utf8(wchar_t uni, unsigned char *out, int boundlen)
973{
974 int n = utf32_to_utf8(uni, out, boundlen);
975
976 if (n < 0) {
977 *out = '?';
978 return -EINVAL;
979 }
980 return n;
981}
982
983/*
984 * Test that hfsplus_asc2uni() brings combining marks contributed by
985 * different source characters into Unicode canonical (combining-class)
986 * order, instead of just storing them in whatever order they were typed.
987 *
988 * U+0301 (COMBINING ACUTE ACCENT) has combining class 230; U+0323
989 * (COMBINING DOT BELOW) has combining class 220. Since 220 < 230,
990 * canonical order requires the dot-below before the acute. A name
991 * created with them typed in the "wrong" order must still end up stored
992 * in canonical order - this is exactly what macOS's own fsck_hfs
993 * (FixDecomps() in CatalogCheck.c) requires and flags as "Illegal name"
994 * when it isn't true.
995 */
996static void hfsplus_asc2uni_combining_reorder_test(struct kunit *test)
997{
998 struct test_mock_sb *mock_sb;
999 struct hfsplus_unistr ustr;
1000 /* "e" + COMBINING ACUTE ACCENT (U+0301) + COMBINING DOT BELOW (U+0323) */
1001 static const char wrong_order[] = "e\xcc\x81\xcc\xa3";
1002 /* "e" + COMBINING DOT BELOW (U+0323) + COMBINING ACUTE ACCENT (U+0301) */
1003 static const char canonical_order[] = "e\xcc\xa3\xcc\x81";
1004 /* Two class-230 marks: GRAVE (U+0300) then ACUTE (U+0301) */
1005 static const char same_class[] = "e\xcc\x80\xcc\x81";
1006 int result;
1007
1008 mock_sb = setup_mock_sb();
1009 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1010
1011 mock_sb->nls.char2uni = test_char2uni_utf8;
1012
1013 /* Typed out of canonical order: must be reordered on storage. */
1014 result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1015 wrong_order, strlen(wrong_order),
1016 HFS_REGULAR_NAME);
1017
1018 KUNIT_EXPECT_EQ(test, 0, result);
1019 KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
1020 KUNIT_EXPECT_EQ(test, 'e', be16_to_cpu(ustr.unicode[0]));
1021 KUNIT_EXPECT_EQ(test, 0x0323, be16_to_cpu(ustr.unicode[1]));
1022 KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[2]));
1023
1024 /* Already in canonical order: must come out unchanged. */
1025 result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1026 canonical_order, strlen(canonical_order),
1027 HFS_REGULAR_NAME);
1028
1029 KUNIT_EXPECT_EQ(test, 0, result);
1030 KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
1031 KUNIT_EXPECT_EQ(test, 'e', be16_to_cpu(ustr.unicode[0]));
1032 KUNIT_EXPECT_EQ(test, 0x0323, be16_to_cpu(ustr.unicode[1]));
1033 KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[2]));
1034
1035 /* Two marks of equal combining class must keep their relative
1036 * (input) order - the sort must be stable, not just "sorted".
1037 */
1038 result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1039 same_class, strlen(same_class),
1040 HFS_REGULAR_NAME);
1041
1042 KUNIT_EXPECT_EQ(test, 0, result);
1043 KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
1044 KUNIT_EXPECT_EQ(test, 'e', be16_to_cpu(ustr.unicode[0]));
1045 KUNIT_EXPECT_EQ(test, 0x0300, be16_to_cpu(ustr.unicode[1]));
1046 KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[2]));
1047
1048 free_mock_sb(mock_sb);
1049}
1050
1051/*
1052 * Test that a name written with combining marks in non-canonical order
1053 * still reads back as valid, correct UTF-8 once reordered - i.e. that
1054 * hfsplus_asc2uni() and hfsplus_uni2asc_str() stay consistent with each
1055 * other across the reordering.
1056 *
1057 * NODECOMPOSE is set so hfsplus_uni2asc_str() doesn't also recompose
1058 * "e" + COMBINING DOT BELOW back into the precomposed U+1EB9 ("e"): that
1059 * composition behavior is real (and already covered elsewhere), but it
1060 * would obscure what this test is actually checking.
1061 */
1062static void hfsplus_unicode_combining_reorder_roundtrip_test(struct kunit *test)
1063{
1064 struct test_mock_sb *mock_sb;
1065 struct hfsplus_unistr ustr;
1066 static const char wrong_order[] = "e\xcc\x81\xcc\xa3";
1067 static const char expected[] = "e\xcc\xa3\xcc\x81"; /* canonical order */
1068 char buf[32];
1069 int len = sizeof(buf);
1070 int result;
1071
1072 mock_sb = setup_mock_sb();
1073 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1074
1075 set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1076 mock_sb->nls.char2uni = test_char2uni_utf8;
1077 mock_sb->nls.uni2char = test_uni2char_utf8;
1078
1079 result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1080 wrong_order, strlen(wrong_order),
1081 HFS_REGULAR_NAME);
1082 KUNIT_EXPECT_EQ(test, 0, result);
1083
1084 result = hfsplus_uni2asc_str(&mock_sb->sb, &ustr, buf, &len);
1085
1086 KUNIT_EXPECT_EQ(test, 0, result);
1087 KUNIT_EXPECT_EQ(test, (int)strlen(expected), len);
1088 KUNIT_EXPECT_MEMEQ(test, expected, buf, len);
1089
1090 free_mock_sb(mock_sb);
1091}
1092
1093/*
1094 * Test that hfsplus_asc2uni() decomposes a character using the corrected
1095 * (post-2002/"Jaguar") canonical decomposition even when Apple Technote
1096 * #1150's own decomposition table doesn't have an entry for it.
1097 *
1098 * U+01F8 (LATIN CAPITAL LETTER N WITH GRAVE) is exactly one of the
1099 * characters macOS's own fsck_hfs (FixDecomps() in CatalogCheck.c) has
1100 * flagged "Illegal name" for since 2002 when found stored undecomposed.
1101 */
1102static void hfsplus_asc2uni_legacy_decomp_test(struct kunit *test)
1103{
1104 struct test_mock_sb *mock_sb;
1105 struct hfsplus_unistr ustr;
1106 static const char input[] = "\xc7\xb8"; /* U+01F8 */
1107 int result;
1108
1109 mock_sb = setup_mock_sb();
1110 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1111
1112 mock_sb->nls.char2uni = test_char2uni_utf8;
1113
1114 result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1115 input, strlen(input), HFS_REGULAR_NAME);
1116
1117 KUNIT_EXPECT_EQ(test, 0, result);
1118 KUNIT_EXPECT_EQ(test, 2, be16_to_cpu(ustr.length));
1119 KUNIT_EXPECT_EQ(test, 'N', be16_to_cpu(ustr.unicode[0]));
1120 KUNIT_EXPECT_EQ(test, 0x0300, be16_to_cpu(ustr.unicode[1]));
1121
1122 free_mock_sb(mock_sb);
1123}
1124
1125/*
1126 * Test that hfsplus_asc2uni() corrects two more of fsck_hfs's known
1127 * legacy decomposition sequences once combining marks from independently
1128 * typed characters end up adjacent:
1129 *
1130 * - GREEK SMALL LETTER ALPHA (U+03B1) + COMBINING VERTICAL LINE ABOVE
1131 * (U+030D) must become U+03B1 + COMBINING ACUTE ACCENT (U+0301).
1132 * - BENGALI LETTER BA (U+09AC) + BENGALI SIGN NUKTA (U+09BC) must become
1133 * the single character BENGALI LETTER RA WITH MIDDLE DIAGONAL (U+09B0).
1134 */
1135static void hfsplus_asc2uni_legacy_seq_fixup_test(struct kunit *test)
1136{
1137 struct test_mock_sb *mock_sb;
1138 struct hfsplus_unistr ustr;
1139 static const char greek_input[] = "\xce\xb1\xcc\x8d"; /* U+03B1 U+030D */
1140 static const char bengali_input[] = "\xe0\xa6\xac\xe0\xa6\xbc"; /* U+09AC U+09BC */
1141 int result;
1142
1143 mock_sb = setup_mock_sb();
1144 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1145
1146 mock_sb->nls.char2uni = test_char2uni_utf8;
1147
1148 result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1149 greek_input, strlen(greek_input),
1150 HFS_REGULAR_NAME);
1151
1152 KUNIT_EXPECT_EQ(test, 0, result);
1153 KUNIT_EXPECT_EQ(test, 2, be16_to_cpu(ustr.length));
1154 KUNIT_EXPECT_EQ(test, 0x03b1, be16_to_cpu(ustr.unicode[0]));
1155 KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[1]));
1156
1157 result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
1158 bengali_input, strlen(bengali_input),
1159 HFS_REGULAR_NAME);
1160
1161 KUNIT_EXPECT_EQ(test, 0, result);
1162 KUNIT_EXPECT_EQ(test, 1, be16_to_cpu(ustr.length));
1163 KUNIT_EXPECT_EQ(test, 0x09b0, be16_to_cpu(ustr.unicode[0]));
1164
1165 free_mock_sb(mock_sb);
1166}
1167
1168/* Mock dentry for testing hfsplus_hash_dentry */
1169static struct dentry test_dentry;
1170
1171static void setup_mock_dentry(struct super_block *sb)
1172{
1173 memset(&test_dentry, 0, sizeof(test_dentry));
1174 test_dentry.d_sb = sb;
1175}
1176
1177/* Helper function to create qstr */
1178static void create_qstr(struct qstr *str, const char *name)
1179{
1180 str->name = name;
1181 str->len = strlen(name);
1182 str->hash = 0; /* Will be set by hash function */
1183}
1184
1185/* Test hfsplus_hash_dentry basic functionality */
1186static void hfsplus_hash_dentry_basic_test(struct kunit *test)
1187{
1188 struct test_mock_sb *mock_sb;
1189 struct qstr str1, str2;
1190 int result;
1191
1192 mock_sb = setup_mock_sb();
1193 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1194
1195 setup_mock_dentry(&mock_sb->sb);
1196 mock_sb->nls.char2uni = test_char2uni;
1197
1198 /* Test basic string hashing */
1199 create_qstr(&str1, "hello");
1200 result = hfsplus_hash_dentry(&test_dentry, &str1);
1201
1202 KUNIT_EXPECT_EQ(test, 0, result);
1203 KUNIT_EXPECT_NE(test, 0, str1.hash);
1204
1205 /* Test that identical strings produce identical hashes */
1206 create_qstr(&str2, "hello");
1207 result = hfsplus_hash_dentry(&test_dentry, &str2);
1208
1209 KUNIT_EXPECT_EQ(test, 0, result);
1210 KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
1211
1212 /* Test empty string */
1213 create_qstr(&str1, "");
1214 result = hfsplus_hash_dentry(&test_dentry, &str1);
1215
1216 /* Empty string should still produce a hash */
1217 KUNIT_EXPECT_EQ(test, 0, result);
1218
1219 /* Test single character */
1220 create_qstr(&str1, "A");
1221 result = hfsplus_hash_dentry(&test_dentry, &str1);
1222
1223 KUNIT_EXPECT_EQ(test, 0, result);
1224 KUNIT_EXPECT_NE(test, 0, str1.hash);
1225
1226 free_mock_sb(mock_sb);
1227}
1228
1229/* Test case folding behavior in hash */
1230static void hfsplus_hash_dentry_casefold_test(struct kunit *test)
1231{
1232 struct test_mock_sb *mock_sb;
1233 struct qstr str1, str2;
1234 int result;
1235
1236 mock_sb = setup_mock_sb();
1237 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1238
1239 setup_mock_dentry(&mock_sb->sb);
1240 mock_sb->nls.char2uni = test_char2uni;
1241
1242 /* Test with case folding disabled (default) */
1243 clear_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
1244
1245 create_qstr(&str1, "Hello");
1246 result = hfsplus_hash_dentry(&test_dentry, &str1);
1247 KUNIT_EXPECT_EQ(test, 0, result);
1248
1249 create_qstr(&str2, "hello");
1250 result = hfsplus_hash_dentry(&test_dentry, &str2);
1251 KUNIT_EXPECT_EQ(test, 0, result);
1252
1253 /*
1254 * Without case folding, different cases
1255 * should produce different hashes
1256 */
1257 KUNIT_EXPECT_NE(test, str1.hash, str2.hash);
1258
1259 /* Test with case folding enabled */
1260 set_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
1261
1262 create_qstr(&str1, "Hello");
1263 result = hfsplus_hash_dentry(&test_dentry, &str1);
1264 KUNIT_EXPECT_EQ(test, 0, result);
1265
1266 create_qstr(&str2, "hello");
1267 result = hfsplus_hash_dentry(&test_dentry, &str2);
1268 KUNIT_EXPECT_EQ(test, 0, result);
1269
1270 /* With case folding, different cases should produce same hash */
1271 KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
1272
1273 /* Test mixed case */
1274 create_qstr(&str1, "HeLLo");
1275 result = hfsplus_hash_dentry(&test_dentry, &str1);
1276 KUNIT_EXPECT_EQ(test, 0, result);
1277 KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
1278
1279 free_mock_sb(mock_sb);
1280}
1281
1282/* Test special character handling in hash */
1283static void hfsplus_hash_dentry_special_chars_test(struct kunit *test)
1284{
1285 struct test_mock_sb *mock_sb;
1286 struct qstr str1, str2;
1287 int result;
1288
1289 mock_sb = setup_mock_sb();
1290 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1291
1292 setup_mock_dentry(&mock_sb->sb);
1293 mock_sb->nls.char2uni = test_char2uni;
1294
1295 /* Test colon conversion (: becomes /) */
1296 create_qstr(&str1, "file:name");
1297 result = hfsplus_hash_dentry(&test_dentry, &str1);
1298 KUNIT_EXPECT_EQ(test, 0, result);
1299
1300 create_qstr(&str2, "file/name");
1301 result = hfsplus_hash_dentry(&test_dentry, &str2);
1302 KUNIT_EXPECT_EQ(test, 0, result);
1303
1304 /* After conversion, these should produce the same hash */
1305 KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
1306
1307 /* Test multiple special characters */
1308 create_qstr(&str1, ":::");
1309 result = hfsplus_hash_dentry(&test_dentry, &str1);
1310 KUNIT_EXPECT_EQ(test, 0, result);
1311
1312 create_qstr(&str2, "///");
1313 result = hfsplus_hash_dentry(&test_dentry, &str2);
1314 KUNIT_EXPECT_EQ(test, 0, result);
1315
1316 KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
1317
1318 free_mock_sb(mock_sb);
1319}
1320
1321/* Test decomposition flag behavior in hash */
1322static void hfsplus_hash_dentry_decompose_test(struct kunit *test)
1323{
1324 struct test_mock_sb *mock_sb;
1325 struct qstr str1, str2;
1326 int result;
1327
1328 mock_sb = setup_mock_sb();
1329 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1330
1331 setup_mock_dentry(&mock_sb->sb);
1332 mock_sb->nls.char2uni = test_char2uni;
1333
1334 /* Test with decomposition disabled (default) */
1335 clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1336
1337 create_qstr(&str1, "test");
1338 result = hfsplus_hash_dentry(&test_dentry, &str1);
1339 KUNIT_EXPECT_EQ(test, 0, result);
1340
1341 /* Test with decomposition enabled */
1342 set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1343
1344 create_qstr(&str2, "test");
1345 result = hfsplus_hash_dentry(&test_dentry, &str2);
1346 KUNIT_EXPECT_EQ(test, 0, result);
1347
1348 /*
1349 * For simple ASCII, decomposition shouldn't change
1350 * the hash much but the function should still work correctly
1351 */
1352 KUNIT_EXPECT_NE(test, 0, str2.hash);
1353
1354 free_mock_sb(mock_sb);
1355}
1356
1357/* Test hash consistency and distribution */
1358static void hfsplus_hash_dentry_consistency_test(struct kunit *test)
1359{
1360 struct test_mock_sb *mock_sb;
1361 struct qstr str1, str2, str3;
1362 unsigned long hash1;
1363 int result;
1364
1365 mock_sb = setup_mock_sb();
1366 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1367
1368 setup_mock_dentry(&mock_sb->sb);
1369 mock_sb->nls.char2uni = test_char2uni;
1370
1371 /* Test that same string always produces same hash */
1372 create_qstr(&str1, "consistent");
1373 result = hfsplus_hash_dentry(&test_dentry, &str1);
1374 KUNIT_EXPECT_EQ(test, 0, result);
1375 hash1 = str1.hash;
1376
1377 create_qstr(&str2, "consistent");
1378 result = hfsplus_hash_dentry(&test_dentry, &str2);
1379 KUNIT_EXPECT_EQ(test, 0, result);
1380
1381 KUNIT_EXPECT_EQ(test, hash1, str2.hash);
1382
1383 /* Test that different strings produce different hashes */
1384 create_qstr(&str3, "different");
1385 result = hfsplus_hash_dentry(&test_dentry, &str3);
1386 KUNIT_EXPECT_EQ(test, 0, result);
1387
1388 KUNIT_EXPECT_NE(test, str1.hash, str3.hash);
1389
1390 /* Test similar strings should have different hashes */
1391 create_qstr(&str1, "file1");
1392 result = hfsplus_hash_dentry(&test_dentry, &str1);
1393 KUNIT_EXPECT_EQ(test, 0, result);
1394
1395 create_qstr(&str2, "file2");
1396 result = hfsplus_hash_dentry(&test_dentry, &str2);
1397 KUNIT_EXPECT_EQ(test, 0, result);
1398
1399 KUNIT_EXPECT_NE(test, str1.hash, str2.hash);
1400
1401 free_mock_sb(mock_sb);
1402}
1403
1404/* Test edge cases and boundary conditions */
1405static void hfsplus_hash_dentry_edge_cases_test(struct kunit *test)
1406{
1407 struct test_mock_sb *mock_sb;
1408 struct test_mock_string_env *mock_env;
1409 struct qstr str;
1410 int result;
1411
1412 mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
1413 KUNIT_ASSERT_NOT_NULL(test, mock_env);
1414
1415 mock_sb = setup_mock_sb();
1416 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1417
1418 setup_mock_dentry(&mock_sb->sb);
1419 mock_sb->nls.char2uni = test_char2uni;
1420
1421 /* Test very long filename */
1422 memset(mock_env->buf, 'a', mock_env->buf_size - 1);
1423 mock_env->buf[mock_env->buf_size - 1] = '\0';
1424
1425 create_qstr(&str, mock_env->buf);
1426 result = hfsplus_hash_dentry(&test_dentry, &str);
1427
1428 KUNIT_EXPECT_EQ(test, 0, result);
1429 KUNIT_EXPECT_NE(test, 0, str.hash);
1430
1431 /* Test filename with all printable ASCII characters */
1432 create_qstr(&str, "!@#$%^&*()_+-=[]{}|;':\",./<>?");
1433 result = hfsplus_hash_dentry(&test_dentry, &str);
1434
1435 KUNIT_EXPECT_EQ(test, 0, result);
1436 KUNIT_EXPECT_NE(test, 0, str.hash);
1437
1438 /* Test with embedded null (though not typical for filenames) */
1439 str.name = "file\0hidden";
1440 str.len = 11; /* Include the null and text after it */
1441 str.hash = 0;
1442 result = hfsplus_hash_dentry(&test_dentry, &str);
1443
1444 KUNIT_EXPECT_EQ(test, 0, result);
1445 KUNIT_EXPECT_NE(test, 0, str.hash);
1446
1447 free_mock_str_env(mock_env);
1448 free_mock_sb(mock_sb);
1449}
1450
1451/*
1452 * Test that hfsplus_hash_dentry() hashes two names identically when they
1453 * differ only in the (non-canonical) typed order of the same combining
1454 * marks - both must canonicalize to the same stored form, so they must
1455 * hash the same or dcache lookups would spuriously miss.
1456 */
1457static void hfsplus_hash_dentry_combining_reorder_test(struct kunit *test)
1458{
1459 struct test_mock_sb *mock_sb;
1460 struct qstr str1, str2;
1461 int result;
1462
1463 mock_sb = setup_mock_sb();
1464 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1465
1466 setup_mock_dentry(&mock_sb->sb);
1467 mock_sb->nls.char2uni = test_char2uni_utf8;
1468
1469 create_qstr(&str1, "e\xcc\x81\xcc\xa3"); /* acute, then dot-below */
1470 result = hfsplus_hash_dentry(&test_dentry, &str1);
1471 KUNIT_EXPECT_EQ(test, 0, result);
1472
1473 create_qstr(&str2, "e\xcc\xa3\xcc\x81"); /* dot-below, then acute */
1474 result = hfsplus_hash_dentry(&test_dentry, &str2);
1475 KUNIT_EXPECT_EQ(test, 0, result);
1476
1477 KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
1478
1479 free_mock_sb(mock_sb);
1480}
1481
1482/* Test hfsplus_compare_dentry basic functionality */
1483static void hfsplus_compare_dentry_basic_test(struct kunit *test)
1484{
1485 struct test_mock_sb *mock_sb;
1486 struct qstr name;
1487 int result;
1488
1489 mock_sb = setup_mock_sb();
1490 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1491
1492 setup_mock_dentry(&mock_sb->sb);
1493 mock_sb->nls.char2uni = test_char2uni;
1494
1495 /* Test identical strings */
1496 create_qstr(&name, "hello");
1497 result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name);
1498 KUNIT_EXPECT_EQ(test, 0, result);
1499
1500 /* Test different strings - lexicographic order */
1501 create_qstr(&name, "world");
1502 result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name);
1503 KUNIT_EXPECT_LT(test, result, 0); /* "hello" < "world" */
1504
1505 result = hfsplus_compare_dentry(&test_dentry, 5, "world", &name);
1506 KUNIT_EXPECT_EQ(test, 0, result);
1507
1508 create_qstr(&name, "hello");
1509 result = hfsplus_compare_dentry(&test_dentry, 5, "world", &name);
1510 KUNIT_EXPECT_GT(test, result, 0); /* "world" > "hello" */
1511
1512 /* Test empty strings */
1513 create_qstr(&name, "");
1514 result = hfsplus_compare_dentry(&test_dentry, 0, "", &name);
1515 KUNIT_EXPECT_EQ(test, 0, result);
1516
1517 /* Test one empty, one non-empty */
1518 create_qstr(&name, "test");
1519 result = hfsplus_compare_dentry(&test_dentry, 0, "", &name);
1520 KUNIT_EXPECT_LT(test, result, 0); /* "" < "test" */
1521
1522 create_qstr(&name, "");
1523 result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
1524 KUNIT_EXPECT_GT(test, result, 0); /* "test" > "" */
1525
1526 free_mock_sb(mock_sb);
1527}
1528
1529/* Test case folding behavior in comparison */
1530static void hfsplus_compare_dentry_casefold_test(struct kunit *test)
1531{
1532 struct test_mock_sb *mock_sb;
1533 struct qstr name;
1534 int result;
1535
1536 mock_sb = setup_mock_sb();
1537 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1538
1539 setup_mock_dentry(&mock_sb->sb);
1540 mock_sb->nls.char2uni = test_char2uni;
1541
1542 /* Test with case folding disabled (default) */
1543 clear_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
1544
1545 create_qstr(&name, "hello");
1546 result = hfsplus_compare_dentry(&test_dentry, 5, "Hello", &name);
1547 /* Case sensitive: "Hello" != "hello" */
1548 KUNIT_EXPECT_NE(test, 0, result);
1549
1550 create_qstr(&name, "Hello");
1551 result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name);
1552 /* Case sensitive: "hello" != "Hello" */
1553 KUNIT_EXPECT_NE(test, 0, result);
1554
1555 /* Test with case folding enabled */
1556 set_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
1557
1558 create_qstr(&name, "hello");
1559 result = hfsplus_compare_dentry(&test_dentry, 5, "Hello", &name);
1560 /* Case insensitive: "Hello" == "hello" */
1561 KUNIT_EXPECT_EQ(test, 0, result);
1562
1563 create_qstr(&name, "Hello");
1564 result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name);
1565 /* Case insensitive: "hello" == "Hello" */
1566 KUNIT_EXPECT_EQ(test, 0, result);
1567
1568 /* Test mixed case */
1569 create_qstr(&name, "TeSt");
1570 result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
1571 KUNIT_EXPECT_EQ(test, 0, result);
1572
1573 create_qstr(&name, "test");
1574 result = hfsplus_compare_dentry(&test_dentry, 4, "TEST", &name);
1575 KUNIT_EXPECT_EQ(test, 0, result);
1576
1577 free_mock_sb(mock_sb);
1578}
1579
1580/* Test special character handling in comparison */
1581static void hfsplus_compare_dentry_special_chars_test(struct kunit *test)
1582{
1583 struct test_mock_sb *mock_sb;
1584 struct qstr name;
1585 int result;
1586
1587 mock_sb = setup_mock_sb();
1588 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1589
1590 setup_mock_dentry(&mock_sb->sb);
1591 mock_sb->nls.char2uni = test_char2uni;
1592
1593 /* Test colon conversion (: becomes /) */
1594 create_qstr(&name, "file/name");
1595 result = hfsplus_compare_dentry(&test_dentry, 9, "file:name", &name);
1596 /* "file:name" == "file/name" after conversion */
1597 KUNIT_EXPECT_EQ(test, 0, result);
1598
1599 create_qstr(&name, "file:name");
1600 result = hfsplus_compare_dentry(&test_dentry, 9, "file/name", &name);
1601 /* "file/name" == "file:name" after conversion */
1602 KUNIT_EXPECT_EQ(test, 0, result);
1603
1604 /* Test multiple special characters */
1605 create_qstr(&name, "///");
1606 result = hfsplus_compare_dentry(&test_dentry, 3, ":::", &name);
1607 KUNIT_EXPECT_EQ(test, 0, result);
1608
1609 /* Test mixed special and regular characters */
1610 create_qstr(&name, "a/b:c");
1611 result = hfsplus_compare_dentry(&test_dentry, 5, "a:b/c", &name);
1612 /* Both become "a/b/c" after conversion */
1613 KUNIT_EXPECT_EQ(test, 0, result);
1614
1615 free_mock_sb(mock_sb);
1616}
1617
1618/* Test length differences */
1619static void hfsplus_compare_dentry_length_test(struct kunit *test)
1620{
1621 struct test_mock_sb *mock_sb;
1622 struct qstr name;
1623 int result;
1624
1625 mock_sb = setup_mock_sb();
1626 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1627
1628 setup_mock_dentry(&mock_sb->sb);
1629 mock_sb->nls.char2uni = test_char2uni;
1630
1631 /* Test different lengths with common prefix */
1632 create_qstr(&name, "testing");
1633 result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
1634 KUNIT_EXPECT_LT(test, result, 0); /* "test" < "testing" */
1635
1636 create_qstr(&name, "test");
1637 result = hfsplus_compare_dentry(&test_dentry, 7, "testing", &name);
1638 KUNIT_EXPECT_GT(test, result, 0); /* "testing" > "test" */
1639
1640 /* Test exact length match */
1641 create_qstr(&name, "exact");
1642 result = hfsplus_compare_dentry(&test_dentry, 5, "exact", &name);
1643 KUNIT_EXPECT_EQ(test, 0, result);
1644
1645 /* Test length parameter vs actual string content */
1646 create_qstr(&name, "hello");
1647 result = hfsplus_compare_dentry(&test_dentry, 3, "hel", &name);
1648 KUNIT_EXPECT_LT(test, result, 0); /* "hel" < "hello" */
1649
1650 /* Test longer first string but shorter length parameter */
1651 create_qstr(&name, "hi");
1652 result = hfsplus_compare_dentry(&test_dentry, 2, "hello", &name);
1653 /* "he" < "hi" (only first 2 chars compared) */
1654 KUNIT_EXPECT_LT(test, result, 0);
1655
1656 free_mock_sb(mock_sb);
1657}
1658
1659/* Test decomposition flag behavior */
1660static void hfsplus_compare_dentry_decompose_test(struct kunit *test)
1661{
1662 struct test_mock_sb *mock_sb;
1663 struct qstr name;
1664 int result;
1665
1666 mock_sb = setup_mock_sb();
1667 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1668
1669 setup_mock_dentry(&mock_sb->sb);
1670 mock_sb->nls.char2uni = test_char2uni;
1671
1672 /* Test with decomposition disabled (default) */
1673 clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1674
1675 create_qstr(&name, "test");
1676 result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
1677 KUNIT_EXPECT_EQ(test, 0, result);
1678
1679 /* Test with decomposition enabled */
1680 set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1681
1682 create_qstr(&name, "test");
1683 result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
1684 KUNIT_EXPECT_EQ(test, 0, result);
1685
1686 /* For simple ASCII, decomposition shouldn't affect the result */
1687 create_qstr(&name, "different");
1688 result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
1689 KUNIT_EXPECT_NE(test, 0, result);
1690
1691 free_mock_sb(mock_sb);
1692}
1693
1694/* Test edge cases and boundary conditions */
1695static void hfsplus_compare_dentry_edge_cases_test(struct kunit *test)
1696{
1697 struct test_mock_sb *mock_sb;
1698 struct qstr name;
1699 char *long_str;
1700 char *long_str2;
1701 u32 str_size = HFSPLUS_MAX_STRLEN + 1;
1702 struct qstr null_name = {
1703 .name = "a\0b",
1704 .len = 3,
1705 .hash = 0
1706 };
1707 int result;
1708
1709 mock_sb = setup_mock_sb();
1710 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1711
1712 setup_mock_dentry(&mock_sb->sb);
1713 mock_sb->nls.char2uni = test_char2uni;
1714
1715 long_str = kzalloc(str_size, GFP_KERNEL);
1716 KUNIT_ASSERT_NOT_NULL(test, long_str);
1717
1718 long_str2 = kzalloc(str_size, GFP_KERNEL);
1719 KUNIT_ASSERT_NOT_NULL(test, long_str2);
1720
1721 /* Test very long strings */
1722 memset(long_str, 'a', str_size - 1);
1723 long_str[str_size - 1] = '\0';
1724
1725 create_qstr(&name, long_str);
1726 result = hfsplus_compare_dentry(&test_dentry, str_size - 1,
1727 long_str, &name);
1728 KUNIT_EXPECT_EQ(test, 0, result);
1729
1730 /* Test with difference at the end of long strings */
1731 memset(long_str2, 'a', str_size - 1);
1732 long_str2[str_size - 1] = '\0';
1733 long_str2[str_size - 2] = 'b';
1734 create_qstr(&name, long_str2);
1735 result = hfsplus_compare_dentry(&test_dentry, str_size - 1,
1736 long_str, &name);
1737 KUNIT_EXPECT_LT(test, result, 0); /* 'a' < 'b' */
1738
1739 /* Test single character differences */
1740 create_qstr(&name, "b");
1741 result = hfsplus_compare_dentry(&test_dentry, 1, "a", &name);
1742 KUNIT_EXPECT_LT(test, result, 0); /* 'a' < 'b' */
1743
1744 create_qstr(&name, "a");
1745 result = hfsplus_compare_dentry(&test_dentry, 1, "b", &name);
1746 KUNIT_EXPECT_GT(test, result, 0); /* 'b' > 'a' */
1747
1748 /* Test with null characters in the middle */
1749 result = hfsplus_compare_dentry(&test_dentry, 3, "a\0b", &null_name);
1750 KUNIT_EXPECT_EQ(test, 0, result);
1751
1752 /* Test all printable ASCII characters */
1753 create_qstr(&name, "!@#$%^&*()");
1754 result = hfsplus_compare_dentry(&test_dentry, 10, "!@#$%^&*()", &name);
1755 KUNIT_EXPECT_EQ(test, 0, result);
1756
1757 kfree(long_str);
1758 kfree(long_str2);
1759 free_mock_sb(mock_sb);
1760}
1761
1762/* Test combined flag behaviors */
1763static void hfsplus_compare_dentry_combined_flags_test(struct kunit *test)
1764{
1765 struct test_mock_sb *mock_sb;
1766 struct qstr name;
1767 int result;
1768
1769 mock_sb = setup_mock_sb();
1770 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1771
1772 setup_mock_dentry(&mock_sb->sb);
1773 mock_sb->nls.char2uni = test_char2uni;
1774
1775 /* Test with both casefold and decompose enabled */
1776 set_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
1777 set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1778
1779 create_qstr(&name, "hello");
1780 result = hfsplus_compare_dentry(&test_dentry, 5, "HELLO", &name);
1781 KUNIT_EXPECT_EQ(test, 0, result);
1782
1783 /* Test special chars with case folding */
1784 create_qstr(&name, "File/Name");
1785 result = hfsplus_compare_dentry(&test_dentry, 9, "file:name", &name);
1786 KUNIT_EXPECT_EQ(test, 0, result);
1787
1788 /* Test with both flags disabled */
1789 clear_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
1790 clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
1791
1792 create_qstr(&name, "hello");
1793 result = hfsplus_compare_dentry(&test_dentry, 5, "HELLO", &name);
1794 KUNIT_EXPECT_NE(test, 0, result); /* Case sensitive */
1795
1796 /* But special chars should still be converted */
1797 create_qstr(&name, "file/name");
1798 result = hfsplus_compare_dentry(&test_dentry, 9, "file:name", &name);
1799 KUNIT_EXPECT_EQ(test, 0, result);
1800
1801 free_mock_sb(mock_sb);
1802}
1803
1804/*
1805 * Test that hfsplus_compare_dentry() treats two names as equal when they
1806 * differ only in the (non-canonical) typed order of the same combining
1807 * marks, since both refer to the same canonically-ordered catalog entry.
1808 */
1809static void hfsplus_compare_dentry_combining_reorder_test(struct kunit *test)
1810{
1811 struct test_mock_sb *mock_sb;
1812 struct qstr name;
1813 int result;
1814
1815 mock_sb = setup_mock_sb();
1816 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1817
1818 setup_mock_dentry(&mock_sb->sb);
1819 mock_sb->nls.char2uni = test_char2uni_utf8;
1820
1821 create_qstr(&name, "e\xcc\xa3\xcc\x81"); /* dot-below, then acute */
1822 result = hfsplus_compare_dentry(&test_dentry, 5, "e\xcc\x81\xcc\xa3",
1823 &name); /* acute, then dot-below */
1824 KUNIT_EXPECT_EQ(test, 0, result);
1825
1826 free_mock_sb(mock_sb);
1827}
1828
1829/*
1830 * Test that a precomposed character using one of fsck_hfs's known legacy
1831 * decompositions compares equal to the already-decomposed form of the
1832 * same character - i.e. that hfsplus_compare_dentry() applies the same
1833 * legacy-decomposition correction as hfsplus_asc2uni() does on storage,
1834 * so a lookup finds the entry regardless of which form was typed.
1835 */
1836static void hfsplus_compare_dentry_legacy_decomp_test(struct kunit *test)
1837{
1838 struct test_mock_sb *mock_sb;
1839 struct qstr name;
1840 int result;
1841
1842 mock_sb = setup_mock_sb();
1843 KUNIT_ASSERT_NOT_NULL(test, mock_sb);
1844
1845 setup_mock_dentry(&mock_sb->sb);
1846 mock_sb->nls.char2uni = test_char2uni_utf8;
1847
1848 /* "N" + COMBINING GRAVE ACCENT (U+0300), already decomposed */
1849 create_qstr(&name, "N\xcc\x80");
1850 /* U+01F8, precomposed */
1851 result = hfsplus_compare_dentry(&test_dentry, 2, "\xc7\xb8", &name);
1852 KUNIT_EXPECT_EQ(test, 0, result);
1853
1854 free_mock_sb(mock_sb);
1855}
1856
1857static struct kunit_case hfsplus_unicode_test_cases[] = {
1858 KUNIT_CASE(hfsplus_strcasecmp_test),
1859 KUNIT_CASE(hfsplus_strcmp_test),
1860 KUNIT_CASE(hfsplus_unicode_edge_cases_test),
1861 KUNIT_CASE(hfsplus_unicode_boundary_test),
1862 KUNIT_CASE(hfsplus_uni2asc_basic_test),
1863 KUNIT_CASE(hfsplus_uni2asc_special_chars_test),
1864 KUNIT_CASE(hfsplus_uni2asc_buffer_test),
1865 KUNIT_CASE(hfsplus_uni2asc_corrupted_test),
1866 KUNIT_CASE(hfsplus_uni2asc_edge_cases_test),
1867 KUNIT_CASE(hfsplus_asc2uni_basic_test),
1868 KUNIT_CASE(hfsplus_asc2uni_special_chars_test),
1869 KUNIT_CASE(hfsplus_asc2uni_buffer_limits_test),
1870 KUNIT_CASE(hfsplus_asc2uni_edge_cases_test),
1871 KUNIT_CASE(hfsplus_asc2uni_decompose_test),
1872 KUNIT_CASE(hfsplus_asc2uni_combining_reorder_test),
1873 KUNIT_CASE(hfsplus_unicode_combining_reorder_roundtrip_test),
1874 KUNIT_CASE(hfsplus_asc2uni_legacy_decomp_test),
1875 KUNIT_CASE(hfsplus_asc2uni_legacy_seq_fixup_test),
1876 KUNIT_CASE(hfsplus_hash_dentry_basic_test),
1877 KUNIT_CASE(hfsplus_hash_dentry_casefold_test),
1878 KUNIT_CASE(hfsplus_hash_dentry_special_chars_test),
1879 KUNIT_CASE(hfsplus_hash_dentry_decompose_test),
1880 KUNIT_CASE(hfsplus_hash_dentry_consistency_test),
1881 KUNIT_CASE(hfsplus_hash_dentry_edge_cases_test),
1882 KUNIT_CASE(hfsplus_hash_dentry_combining_reorder_test),
1883 KUNIT_CASE(hfsplus_compare_dentry_basic_test),
1884 KUNIT_CASE(hfsplus_compare_dentry_casefold_test),
1885 KUNIT_CASE(hfsplus_compare_dentry_special_chars_test),
1886 KUNIT_CASE(hfsplus_compare_dentry_length_test),
1887 KUNIT_CASE(hfsplus_compare_dentry_decompose_test),
1888 KUNIT_CASE(hfsplus_compare_dentry_edge_cases_test),
1889 KUNIT_CASE(hfsplus_compare_dentry_combined_flags_test),
1890 KUNIT_CASE(hfsplus_compare_dentry_combining_reorder_test),
1891 KUNIT_CASE(hfsplus_compare_dentry_legacy_decomp_test),
1892 {}
1893};
1894
1895static struct kunit_suite hfsplus_unicode_test_suite = {
1896 .name = "hfsplus_unicode",
1897 .test_cases = hfsplus_unicode_test_cases,
1898};
1899
1900kunit_test_suite(hfsplus_unicode_test_suite);
1901
1902MODULE_DESCRIPTION("KUnit tests for HFS+ Unicode string operations");
1903MODULE_LICENSE("GPL");
1904MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");