1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Written by Ojaswin Mujoo <ojaswin@linux.ibm.com> (IBM) 4 * 5 * These Kunit tests are designed to test the functionality of 6 * extent split and conversion in ext4. 7 * 8 * Currently, ext4 can split extents in 2 ways: 9 * 1. By splitting the extents in the extent tree and optionally converting them 10 * to written or unwritten based on flags passed. 11 * 2. In case 1 encounters an error, ext4 instead zerooes out the unwritten 12 * areas of the extent and marks the complete extent written. 13 * 14 * The primary function that handles this is ext4_split_convert_extents(). 15 * 16 * We test both of the methods of split. The behavior we try to enforce is: 17 * 1. When passing EXT4_GET_BLOCKS_CONVERT flag to ext4_split_convert_extents(), 18 * the split extent should be converted to initialized. 19 * 2. When passing EXT4_GET_BLOCKS_CONVERT_UNWRITTEN flag to 20 * ext4_split_convert_extents(), the split extent should be converted to 21 * uninitialized. 22 * 3. In case we use the zeroout method, then we should correctly write zeroes 23 * to the unwritten areas of the extent and we should not corrupt/leak any 24 * data. 25 * 26 * Enforcing 1 and 2 is straight forward, we just setup a minimal inode with 27 * extent tree, call ext4_split_convert_extents() and check the final state of 28 * the extent tree. 29 * 30 * For zeroout testing, we maintain a separate buffer which represents the disk 31 * data corresponding to the extents. We then override ext4's zeroout functions 32 * to instead write zeroes to our buffer. Then, we override 33 * ext4_ext_insert_extent() to return -ENOSPC, which triggers the zeroout. 34 * Finally, we check the state of the extent tree and zeroout buffer to confirm 35 * everything went well. 36 */ 37 38 #include <kunit/test.h> 39 #include <kunit/static_stub.h> 40 #include <linux/gfp_types.h> 41 #include <linux/stddef.h> 42 43 #include "ext4.h" 44 #include "ext4_extents.h" 45 46 #define EXT_DATA_PBLK 100 47 #define EXT_DATA_LBLK 10 48 #define EXT_DATA_LEN 3 49 50 struct kunit_ctx { 51 /* 52 * Ext4 inode which has only 1 unwrit extent 53 */ 54 struct ext4_inode_info *k_ei; 55 /* 56 * Represents the underlying data area (used for zeroout testing) 57 */ 58 char *k_data; 59 } k_ctx; 60 61 /* 62 * describes the state of an expected extent in extent tree. 63 */ 64 struct kunit_ext_state { 65 ext4_lblk_t ex_lblk; 66 ext4_lblk_t ex_len; 67 bool is_unwrit; 68 }; 69 70 /* 71 * describes the state of the data area of a writ extent. Used for testing 72 * correctness of zeroout. 73 */ 74 struct kunit_ext_data_state { 75 char exp_char; 76 ext4_lblk_t off_blk; 77 ext4_lblk_t len_blk; 78 }; 79 80 enum kunit_test_types { 81 TEST_SPLIT_CONVERT, 82 TEST_CREATE_BLOCKS, 83 }; 84 85 struct kunit_ext_test_param { 86 /* description of test */ 87 char *desc; 88 89 /* determines which function will be tested */ 90 int type; 91 92 /* is extent unwrit at beginning of test */ 93 bool is_unwrit_at_start; 94 95 /* flags to pass while splitting */ 96 int split_flags; 97 98 /* map describing range to split */ 99 struct ext4_map_blocks split_map; 100 101 /* disable zeroout */ 102 bool disable_zeroout; 103 104 /* no of extents expected after split */ 105 int nr_exp_ext; 106 107 /* 108 * expected state of extents after split. We will never split into more 109 * than 3 extents 110 */ 111 struct kunit_ext_state exp_ext_state[3]; 112 113 /* Below fields used for zeroout tests */ 114 115 bool is_zeroout_test; 116 /* 117 * no of expected data segments (zeroout tests). Example, if we expect 118 * data to be 4kb 0s, followed by 8kb non-zero, then nr_exp_data_segs==2 119 */ 120 int nr_exp_data_segs; 121 122 /* 123 * expected state of data area after zeroout. 124 */ 125 struct kunit_ext_data_state exp_data_state[3]; 126 }; 127 128 static void ext_kill_sb(struct super_block *sb) 129 { 130 generic_shutdown_super(sb); 131 } 132 133 static int ext_set(struct super_block *sb, void *data) 134 { 135 return 0; 136 } 137 138 static struct file_system_type ext_fs_type = { 139 .name = "extents test", 140 .kill_sb = ext_kill_sb, 141 }; 142 143 static void extents_kunit_exit(struct kunit *test) 144 { 145 struct ext4_sb_info *sbi = k_ctx.k_ei->vfs_inode.i_sb->s_fs_info; 146 147 kfree(sbi); 148 kfree(k_ctx.k_ei); 149 kfree(k_ctx.k_data); 150 } 151 152 static void ext4_cache_extents_stub(struct inode *inode, 153 struct ext4_extent_header *eh) 154 { 155 return; 156 } 157 158 static int __ext4_ext_dirty_stub(const char *where, unsigned int line, 159 handle_t *handle, struct inode *inode, 160 struct ext4_ext_path *path) 161 { 162 return 0; 163 } 164 165 static struct ext4_ext_path * 166 ext4_ext_insert_extent_stub(handle_t *handle, struct inode *inode, 167 struct ext4_ext_path *path, 168 struct ext4_extent *newext, int gb_flags) 169 { 170 return ERR_PTR(-ENOSPC); 171 } 172 173 static void ext4_es_remove_extent_stub(struct inode *inode, ext4_lblk_t lblk, 174 ext4_lblk_t len) 175 { 176 return; 177 } 178 179 void ext4_es_insert_extent_stub(struct inode *inode, ext4_lblk_t lblk, 180 ext4_lblk_t len, ext4_fsblk_t pblk, 181 unsigned int status, bool delalloc_reserve_used) 182 { 183 return; 184 } 185 186 static void ext4_zeroout_es_stub(struct inode *inode, struct ext4_extent *ex) 187 { 188 return; 189 } 190 191 /* 192 * We will zeroout the equivalent range in the data area 193 */ 194 static int ext4_ext_zeroout_stub(struct inode *inode, struct ext4_extent *ex) 195 { 196 ext4_lblk_t ee_block, off_blk; 197 loff_t ee_len; 198 loff_t off_bytes; 199 struct kunit *test = kunit_get_current_test(); 200 201 ee_block = le32_to_cpu(ex->ee_block); 202 ee_len = ext4_ext_get_actual_len(ex); 203 204 KUNIT_EXPECT_EQ_MSG(test, 1, ee_block >= EXT_DATA_LBLK, "ee_block=%d", 205 ee_block); 206 KUNIT_EXPECT_EQ(test, 1, 207 ee_block + ee_len <= EXT_DATA_LBLK + EXT_DATA_LEN); 208 209 off_blk = ee_block - EXT_DATA_LBLK; 210 off_bytes = off_blk << inode->i_sb->s_blocksize_bits; 211 memset(k_ctx.k_data + off_bytes, 0, 212 ee_len << inode->i_sb->s_blocksize_bits); 213 214 return 0; 215 } 216 217 static int ext4_issue_zeroout_stub(struct inode *inode, ext4_lblk_t lblk, 218 ext4_fsblk_t pblk, ext4_lblk_t len) 219 { 220 ext4_lblk_t off_blk; 221 loff_t off_bytes; 222 struct kunit *test = kunit_get_current_test(); 223 224 kunit_log(KERN_ALERT, test, 225 "%s: lblk=%u pblk=%llu len=%u", __func__, lblk, pblk, len); 226 KUNIT_EXPECT_EQ(test, 1, lblk >= EXT_DATA_LBLK); 227 KUNIT_EXPECT_EQ(test, 1, lblk + len <= EXT_DATA_LBLK + EXT_DATA_LEN); 228 KUNIT_EXPECT_EQ(test, 1, lblk - EXT_DATA_LBLK == pblk - EXT_DATA_PBLK); 229 230 off_blk = lblk - EXT_DATA_LBLK; 231 off_bytes = off_blk << inode->i_sb->s_blocksize_bits; 232 memset(k_ctx.k_data + off_bytes, 0, 233 len << inode->i_sb->s_blocksize_bits); 234 235 return 0; 236 } 237 238 static int extents_kunit_init(struct kunit *test) 239 { 240 struct ext4_extent_header *eh = NULL; 241 struct ext4_inode_info *ei; 242 struct inode *inode; 243 struct super_block *sb; 244 struct ext4_sb_info *sbi = NULL; 245 struct kunit_ext_test_param *param = 246 (struct kunit_ext_test_param *)(test->param_value); 247 248 /* setup the mock inode */ 249 k_ctx.k_ei = kzalloc(sizeof(struct ext4_inode_info), GFP_KERNEL); 250 if (k_ctx.k_ei == NULL) 251 return -ENOMEM; 252 ei = k_ctx.k_ei; 253 inode = &ei->vfs_inode; 254 255 sb = sget(&ext_fs_type, NULL, ext_set, 0, NULL); 256 if (IS_ERR(sb)) 257 return PTR_ERR(sb); 258 259 sb->s_blocksize = 4096; 260 sb->s_blocksize_bits = 12; 261 262 sbi = kzalloc(sizeof(struct ext4_sb_info), GFP_KERNEL); 263 if (sbi == NULL) 264 return -ENOMEM; 265 266 sbi->s_sb = sb; 267 sb->s_fs_info = sbi; 268 269 if (!param || !param->disable_zeroout) 270 sbi->s_extent_max_zeroout_kb = 32; 271 272 ei->i_disksize = (EXT_DATA_LBLK + EXT_DATA_LEN + 10) 273 << sb->s_blocksize_bits; 274 ei->i_flags = 0; 275 ext4_set_inode_flag(inode, EXT4_INODE_EXTENTS); 276 inode->i_sb = sb; 277 278 k_ctx.k_data = kzalloc(EXT_DATA_LEN * 4096, GFP_KERNEL); 279 if (k_ctx.k_data == NULL) 280 return -ENOMEM; 281 282 /* 283 * set the data area to a junk value 284 */ 285 memset(k_ctx.k_data, 'X', EXT_DATA_LEN * 4096); 286 287 /* create a tree with depth 0 */ 288 eh = (struct ext4_extent_header *)k_ctx.k_ei->i_data; 289 290 /* Fill extent header */ 291 eh = ext_inode_hdr(&k_ctx.k_ei->vfs_inode); 292 eh->eh_depth = 0; 293 eh->eh_entries = cpu_to_le16(1); 294 eh->eh_magic = EXT4_EXT_MAGIC; 295 eh->eh_max = 296 cpu_to_le16(ext4_ext_space_root_idx(&k_ctx.k_ei->vfs_inode, 0)); 297 eh->eh_generation = 0; 298 299 /* 300 * add 1 extent in leaf node covering: 301 * - lblks: [EXT_DATA_LBLK, EXT_DATA_LBLK * + EXT_DATA_LEN) 302 * - pblks: [EXT_DATA_PBLK, EXT_DATA_PBLK + EXT_DATA_LEN) 303 */ 304 EXT_FIRST_EXTENT(eh)->ee_block = cpu_to_le32(EXT_DATA_LBLK); 305 EXT_FIRST_EXTENT(eh)->ee_len = cpu_to_le16(EXT_DATA_LEN); 306 ext4_ext_store_pblock(EXT_FIRST_EXTENT(eh), EXT_DATA_PBLK); 307 if (!param || param->is_unwrit_at_start) 308 ext4_ext_mark_unwritten(EXT_FIRST_EXTENT(eh)); 309 310 /* Add stubs */ 311 kunit_activate_static_stub(test, ext4_cache_extents, 312 ext4_cache_extents_stub); 313 kunit_activate_static_stub(test, __ext4_ext_dirty, 314 __ext4_ext_dirty_stub); 315 kunit_activate_static_stub(test, ext4_es_remove_extent, 316 ext4_es_remove_extent_stub); 317 kunit_activate_static_stub(test, ext4_es_insert_extent, 318 ext4_es_insert_extent_stub); 319 kunit_activate_static_stub(test, ext4_zeroout_es, ext4_zeroout_es_stub); 320 kunit_activate_static_stub(test, ext4_ext_zeroout, ext4_ext_zeroout_stub); 321 kunit_activate_static_stub(test, ext4_issue_zeroout, 322 ext4_issue_zeroout_stub); 323 return 0; 324 } 325 326 /* 327 * Return 1 if all bytes in the buf equal to c, else return the offset of first mismatch 328 */ 329 static int check_buffer(char *buf, int c, int size) 330 { 331 void *ret = NULL; 332 333 ret = memchr_inv(buf, c, size); 334 if (ret == NULL) 335 return 0; 336 337 kunit_log(KERN_ALERT, kunit_get_current_test(), 338 "# %s: wrong char found at offset %u (expected:%d got:%d)", __func__, 339 (u32)((char *)ret - buf), c, *((char *)ret)); 340 return 1; 341 } 342 343 /* 344 * Simulate a map block call by first calling ext4_map_query_blocks() to 345 * correctly populate map flags and pblk and then call the 346 * ext4_map_create_blocks() to do actual split and conversion. This is easier 347 * than calling ext4_map_blocks() because that needs mocking a lot of unrelated 348 * functions. 349 */ 350 static void ext4_map_create_blocks_helper(struct kunit *test, 351 struct inode *inode, 352 struct ext4_map_blocks *map, 353 int flags) 354 { 355 int retval = 0; 356 357 retval = ext4_map_query_blocks(NULL, inode, map, flags); 358 if (retval < 0) { 359 KUNIT_FAIL(test, 360 "ext4_map_query_blocks() failed. Cannot proceed\n"); 361 return; 362 } 363 364 ext4_map_create_blocks(NULL, inode, map, flags); 365 } 366 367 static void test_split_convert(struct kunit *test) 368 { 369 struct ext4_ext_path *path; 370 struct inode *inode = &k_ctx.k_ei->vfs_inode; 371 struct ext4_extent *ex; 372 struct ext4_map_blocks map; 373 const struct kunit_ext_test_param *param = 374 (const struct kunit_ext_test_param *)(test->param_value); 375 int blkbits = inode->i_sb->s_blocksize_bits; 376 377 if (param->is_zeroout_test) 378 /* 379 * Force zeroout by making ext4_ext_insert_extent return ENOSPC 380 */ 381 kunit_activate_static_stub(test, ext4_ext_insert_extent, 382 ext4_ext_insert_extent_stub); 383 384 path = ext4_find_extent(inode, EXT_DATA_LBLK, NULL, 0); 385 ex = path->p_ext; 386 KUNIT_EXPECT_EQ(test, EXT_DATA_LBLK, le32_to_cpu(ex->ee_block)); 387 KUNIT_EXPECT_EQ(test, EXT_DATA_LEN, ext4_ext_get_actual_len(ex)); 388 KUNIT_EXPECT_EQ(test, param->is_unwrit_at_start, 389 ext4_ext_is_unwritten(ex)); 390 if (param->is_zeroout_test) 391 KUNIT_EXPECT_EQ(test, 0, 392 check_buffer(k_ctx.k_data, 'X', 393 EXT_DATA_LEN << blkbits)); 394 395 map.m_lblk = param->split_map.m_lblk; 396 map.m_len = param->split_map.m_len; 397 398 switch (param->type) { 399 case TEST_SPLIT_CONVERT: 400 path = ext4_split_convert_extents(NULL, inode, &map, path, 401 param->split_flags, NULL); 402 break; 403 case TEST_CREATE_BLOCKS: 404 ext4_map_create_blocks_helper(test, inode, &map, param->split_flags); 405 break; 406 default: 407 KUNIT_FAIL(test, "param->type %d not support.", param->type); 408 } 409 410 path = ext4_find_extent(inode, EXT_DATA_LBLK, NULL, 0); 411 ex = path->p_ext; 412 413 for (int i = 0; i < param->nr_exp_ext; i++) { 414 struct kunit_ext_state exp_ext = param->exp_ext_state[i]; 415 416 KUNIT_EXPECT_EQ(test, exp_ext.ex_lblk, 417 le32_to_cpu(ex->ee_block)); 418 KUNIT_EXPECT_EQ(test, exp_ext.ex_len, 419 ext4_ext_get_actual_len(ex)); 420 KUNIT_EXPECT_EQ(test, exp_ext.is_unwrit, 421 ext4_ext_is_unwritten(ex)); 422 423 /* Only printed on failure */ 424 kunit_log(KERN_INFO, test, 425 "# [extent %d] exp: lblk:%d len:%d unwrit:%d \n", i, 426 exp_ext.ex_lblk, exp_ext.ex_len, exp_ext.is_unwrit); 427 kunit_log(KERN_INFO, test, 428 "# [extent %d] got: lblk:%d len:%d unwrit:%d\n", i, 429 le32_to_cpu(ex->ee_block), 430 ext4_ext_get_actual_len(ex), 431 ext4_ext_is_unwritten(ex)); 432 kunit_log(KERN_INFO, test, "------------------\n"); 433 434 ex = ex + 1; 435 } 436 437 if (!param->is_zeroout_test) 438 return; 439 440 /* 441 * Check that then data area has been zeroed out correctly 442 */ 443 for (int i = 0; i < param->nr_exp_data_segs; i++) { 444 loff_t off, len; 445 struct kunit_ext_data_state exp_data_seg = param->exp_data_state[i]; 446 447 off = exp_data_seg.off_blk << blkbits; 448 len = exp_data_seg.len_blk << blkbits; 449 KUNIT_EXPECT_EQ_MSG(test, 0, 450 check_buffer(k_ctx.k_data + off, 451 exp_data_seg.exp_char, len), 452 "# corruption in byte range [%lld, %lld)", 453 off, len); 454 } 455 456 return; 457 } 458 459 static const struct kunit_ext_test_param test_split_convert_params[] = { 460 /* unwrit to writ splits */ 461 { .desc = "split unwrit extent to 2 extents and convert 1st half writ", 462 .type = TEST_SPLIT_CONVERT, 463 .is_unwrit_at_start = 1, 464 .split_flags = EXT4_GET_BLOCKS_CONVERT, 465 .split_map = { .m_lblk = EXT_DATA_LBLK, .m_len = 1 }, 466 .nr_exp_ext = 2, 467 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 468 .ex_len = 1, 469 .is_unwrit = 0 }, 470 { .ex_lblk = EXT_DATA_LBLK + 1, 471 .ex_len = EXT_DATA_LEN - 1, 472 .is_unwrit = 1 } }, 473 .is_zeroout_test = 0 }, 474 { .desc = "split unwrit extent to 2 extents and convert 2nd half writ", 475 .type = TEST_SPLIT_CONVERT, 476 .is_unwrit_at_start = 1, 477 .split_flags = EXT4_GET_BLOCKS_CONVERT, 478 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 1 }, 479 .nr_exp_ext = 2, 480 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 481 .ex_len = 1, 482 .is_unwrit = 1 }, 483 { .ex_lblk = EXT_DATA_LBLK + 1, 484 .ex_len = EXT_DATA_LEN - 1, 485 .is_unwrit = 0 } }, 486 .is_zeroout_test = 0 }, 487 { .desc = "split unwrit extent to 3 extents and convert 2nd half to writ", 488 .type = TEST_SPLIT_CONVERT, 489 .is_unwrit_at_start = 1, 490 .split_flags = EXT4_GET_BLOCKS_CONVERT, 491 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 2 }, 492 .nr_exp_ext = 3, 493 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 494 .ex_len = 1, 495 .is_unwrit = 1 }, 496 { .ex_lblk = EXT_DATA_LBLK + 1, 497 .ex_len = EXT_DATA_LEN - 2, 498 .is_unwrit = 0 }, 499 { .ex_lblk = EXT_DATA_LBLK + 1 + (EXT_DATA_LEN - 2), 500 .ex_len = 1, 501 .is_unwrit = 1 } }, 502 .is_zeroout_test = 0 }, 503 504 /* writ to unwrit splits */ 505 { .desc = "split writ extent to 2 extents and convert 1st half unwrit", 506 .type = TEST_SPLIT_CONVERT, 507 .is_unwrit_at_start = 0, 508 .split_flags = EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, 509 .split_map = { .m_lblk = EXT_DATA_LBLK, .m_len = 1 }, 510 .nr_exp_ext = 2, 511 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 512 .ex_len = 1, 513 .is_unwrit = 1 }, 514 { .ex_lblk = EXT_DATA_LBLK + 1, 515 .ex_len = EXT_DATA_LEN - 1, 516 .is_unwrit = 0 } }, 517 .is_zeroout_test = 0 }, 518 { .desc = "split writ extent to 2 extents and convert 2nd half unwrit", 519 .type = TEST_SPLIT_CONVERT, 520 .is_unwrit_at_start = 0, 521 .split_flags = EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, 522 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 1 }, 523 .nr_exp_ext = 2, 524 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 525 .ex_len = 1, 526 .is_unwrit = 0 }, 527 { .ex_lblk = EXT_DATA_LBLK + 1, 528 .ex_len = EXT_DATA_LEN - 1, 529 .is_unwrit = 1 } }, 530 .is_zeroout_test = 0 }, 531 { .desc = "split writ extent to 3 extents and convert 2nd half to unwrit", 532 .type = TEST_SPLIT_CONVERT, 533 .is_unwrit_at_start = 0, 534 .split_flags = EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, 535 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 2 }, 536 .nr_exp_ext = 3, 537 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 538 .ex_len = 1, 539 .is_unwrit = 0 }, 540 { .ex_lblk = EXT_DATA_LBLK + 1, 541 .ex_len = EXT_DATA_LEN - 2, 542 .is_unwrit = 1 }, 543 { .ex_lblk = EXT_DATA_LBLK + 1 + (EXT_DATA_LEN - 2), 544 .ex_len = 1, 545 .is_unwrit = 0 } }, 546 .is_zeroout_test = 0 }, 547 548 /* 549 * ***** zeroout tests ***** 550 */ 551 /* unwrit to writ splits */ 552 { .desc = "split unwrit extent to 2 extents and convert 1st half writ (zeroout)", 553 .type = TEST_SPLIT_CONVERT, 554 .is_unwrit_at_start = 1, 555 .split_flags = EXT4_GET_BLOCKS_CONVERT, 556 .split_map = { .m_lblk = EXT_DATA_LBLK, .m_len = 1 }, 557 .nr_exp_ext = 1, 558 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 559 .ex_len = EXT_DATA_LEN, 560 .is_unwrit = 0 } }, 561 .is_zeroout_test = 1, 562 .nr_exp_data_segs = 2, 563 .exp_data_state = { { .exp_char = 'X', .off_blk = 0, .len_blk = 1 }, 564 { .exp_char = 0, 565 .off_blk = 1, 566 .len_blk = EXT_DATA_LEN - 1 } } }, 567 { .desc = "split unwrit extent to 2 extents and convert 2nd half writ (zeroout)", 568 .type = TEST_SPLIT_CONVERT, 569 .is_unwrit_at_start = 1, 570 .split_flags = EXT4_GET_BLOCKS_CONVERT, 571 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 1 }, 572 .nr_exp_ext = 1, 573 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 574 .ex_len = EXT_DATA_LEN, 575 .is_unwrit = 0 } }, 576 .is_zeroout_test = 1, 577 .nr_exp_data_segs = 2, 578 .exp_data_state = { { .exp_char = 0, .off_blk = 0, .len_blk = 1 }, 579 { .exp_char = 'X', 580 .off_blk = 1, 581 .len_blk = EXT_DATA_LEN - 1 } } }, 582 { .desc = "split unwrit extent to 3 extents and convert 2nd half writ (zeroout)", 583 .type = TEST_SPLIT_CONVERT, 584 .is_unwrit_at_start = 1, 585 .split_flags = EXT4_GET_BLOCKS_CONVERT, 586 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 2 }, 587 .nr_exp_ext = 1, 588 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 589 .ex_len = EXT_DATA_LEN, 590 .is_unwrit = 0 } }, 591 .is_zeroout_test = 1, 592 .nr_exp_data_segs = 3, 593 .exp_data_state = { { .exp_char = 0, .off_blk = 0, .len_blk = 1 }, 594 { .exp_char = 'X', 595 .off_blk = 1, 596 .len_blk = EXT_DATA_LEN - 2 }, 597 { .exp_char = 0, 598 .off_blk = EXT_DATA_LEN - 1, 599 .len_blk = 1 } } }, 600 }; 601 602 /* Tests to trigger ext4_ext_map_blocks() -> convert_initialized_extent() */ 603 static const struct kunit_ext_test_param test_convert_initialized_params[] = { 604 /* writ to unwrit splits */ 605 { .desc = "split writ extent to 2 extents and convert 1st half unwrit", 606 .type = TEST_CREATE_BLOCKS, 607 .split_flags = EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, 608 .is_unwrit_at_start = 0, 609 .split_map = { .m_lblk = EXT_DATA_LBLK, .m_len = 1 }, 610 .nr_exp_ext = 2, 611 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 612 .ex_len = 1, 613 .is_unwrit = 1 }, 614 { .ex_lblk = EXT_DATA_LBLK + 1, 615 .ex_len = EXT_DATA_LEN - 1, 616 .is_unwrit = 0 } }, 617 .is_zeroout_test = 0 }, 618 { .desc = "split writ extent to 2 extents and convert 2nd half unwrit", 619 .type = TEST_CREATE_BLOCKS, 620 .split_flags = EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, 621 .is_unwrit_at_start = 0, 622 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 1 }, 623 .nr_exp_ext = 2, 624 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 625 .ex_len = 1, 626 .is_unwrit = 0 }, 627 { .ex_lblk = EXT_DATA_LBLK + 1, 628 .ex_len = EXT_DATA_LEN - 1, 629 .is_unwrit = 1 } }, 630 .is_zeroout_test = 0 }, 631 { .desc = "split writ extent to 3 extents and convert 2nd half to unwrit", 632 .type = TEST_CREATE_BLOCKS, 633 .split_flags = EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, 634 .is_unwrit_at_start = 0, 635 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 2 }, 636 .nr_exp_ext = 3, 637 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 638 .ex_len = 1, 639 .is_unwrit = 0 }, 640 { .ex_lblk = EXT_DATA_LBLK + 1, 641 .ex_len = EXT_DATA_LEN - 2, 642 .is_unwrit = 1 }, 643 { .ex_lblk = EXT_DATA_LBLK + 1 + (EXT_DATA_LEN - 2), 644 .ex_len = 1, 645 .is_unwrit = 0 } }, 646 .is_zeroout_test = 0 }, 647 }; 648 649 /* Tests to trigger ext4_ext_map_blocks() -> ext4_ext_handle_unwritten_exntents() */ 650 static const struct kunit_ext_test_param test_handle_unwritten_params[] = { 651 /* unwrit to writ splits via endio path */ 652 { .desc = "split unwrit extent to 2 extents and convert 1st half writ (endio)", 653 .type = TEST_CREATE_BLOCKS, 654 .is_unwrit_at_start = 1, 655 .split_flags = EXT4_GET_BLOCKS_CONVERT, 656 .split_map = { .m_lblk = EXT_DATA_LBLK, .m_len = 1 }, 657 .nr_exp_ext = 2, 658 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 659 .ex_len = 1, 660 .is_unwrit = 0 }, 661 { .ex_lblk = EXT_DATA_LBLK + 1, 662 .ex_len = EXT_DATA_LEN - 1, 663 .is_unwrit = 1 } }, 664 .is_zeroout_test = 0 }, 665 { .desc = "split unwrit extent to 2 extents and convert 2nd half writ (endio)", 666 .type = TEST_CREATE_BLOCKS, 667 .is_unwrit_at_start = 1, 668 .split_flags = EXT4_GET_BLOCKS_CONVERT, 669 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 1 }, 670 .nr_exp_ext = 2, 671 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 672 .ex_len = 1, 673 .is_unwrit = 1 }, 674 { .ex_lblk = EXT_DATA_LBLK + 1, 675 .ex_len = EXT_DATA_LEN - 1, 676 .is_unwrit = 0 } }, 677 .is_zeroout_test = 0 }, 678 { .desc = "split unwrit extent to 3 extents and convert 2nd half to writ (endio)", 679 .type = TEST_CREATE_BLOCKS, 680 .is_unwrit_at_start = 1, 681 .split_flags = EXT4_GET_BLOCKS_CONVERT, 682 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 2 }, 683 .nr_exp_ext = 3, 684 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 685 .ex_len = 1, 686 .is_unwrit = 1 }, 687 { .ex_lblk = EXT_DATA_LBLK + 1, 688 .ex_len = EXT_DATA_LEN - 2, 689 .is_unwrit = 0 }, 690 { .ex_lblk = EXT_DATA_LBLK + 1 + (EXT_DATA_LEN - 2), 691 .ex_len = 1, 692 .is_unwrit = 1 } }, 693 .is_zeroout_test = 0 }, 694 695 /* unwrit to writ splits via non-endio path */ 696 { .desc = "split unwrit extent to 2 extents and convert 1st half writ (non endio)", 697 .type = TEST_CREATE_BLOCKS, 698 .is_unwrit_at_start = 1, 699 .split_flags = EXT4_GET_BLOCKS_CREATE, 700 .split_map = { .m_lblk = EXT_DATA_LBLK, .m_len = 1 }, 701 .nr_exp_ext = 2, 702 .disable_zeroout = true, 703 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 704 .ex_len = 1, 705 .is_unwrit = 0 }, 706 { .ex_lblk = EXT_DATA_LBLK + 1, 707 .ex_len = EXT_DATA_LEN - 1, 708 .is_unwrit = 1 } }, 709 .is_zeroout_test = 0 }, 710 { .desc = "split unwrit extent to 2 extents and convert 2nd half writ (non endio)", 711 .type = TEST_CREATE_BLOCKS, 712 .is_unwrit_at_start = 1, 713 .split_flags = EXT4_GET_BLOCKS_CREATE, 714 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 1 }, 715 .nr_exp_ext = 2, 716 .disable_zeroout = true, 717 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 718 .ex_len = 1, 719 .is_unwrit = 1 }, 720 { .ex_lblk = EXT_DATA_LBLK + 1, 721 .ex_len = EXT_DATA_LEN - 1, 722 .is_unwrit = 0 } }, 723 .is_zeroout_test = 0 }, 724 { .desc = "split unwrit extent to 3 extents and convert 2nd half to writ (non endio)", 725 .type = TEST_CREATE_BLOCKS, 726 .is_unwrit_at_start = 1, 727 .split_flags = EXT4_GET_BLOCKS_CREATE, 728 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 2 }, 729 .nr_exp_ext = 3, 730 .disable_zeroout = true, 731 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 732 .ex_len = 1, 733 .is_unwrit = 1 }, 734 { .ex_lblk = EXT_DATA_LBLK + 1, 735 .ex_len = EXT_DATA_LEN - 2, 736 .is_unwrit = 0 }, 737 { .ex_lblk = EXT_DATA_LBLK + 1 + (EXT_DATA_LEN - 2), 738 .ex_len = 1, 739 .is_unwrit = 1 } }, 740 .is_zeroout_test = 0 }, 741 742 /* 743 * ***** zeroout tests ***** 744 */ 745 /* unwrit to writ splits (endio)*/ 746 { .desc = "split unwrit extent to 2 extents and convert 1st half writ (endio, zeroout)", 747 .type = TEST_CREATE_BLOCKS, 748 .is_unwrit_at_start = 1, 749 .split_flags = EXT4_GET_BLOCKS_CONVERT, 750 .split_map = { .m_lblk = EXT_DATA_LBLK, .m_len = 1 }, 751 .nr_exp_ext = 1, 752 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 753 .ex_len = EXT_DATA_LEN, 754 .is_unwrit = 0 } }, 755 .is_zeroout_test = 1, 756 .nr_exp_data_segs = 2, 757 .exp_data_state = { { .exp_char = 'X', .off_blk = 0, .len_blk = 1 }, 758 { .exp_char = 0, 759 .off_blk = 1, 760 .len_blk = EXT_DATA_LEN - 1 } } }, 761 { .desc = "split unwrit extent to 2 extents and convert 2nd half writ (endio, zeroout)", 762 .type = TEST_CREATE_BLOCKS, 763 .is_unwrit_at_start = 1, 764 .split_flags = EXT4_GET_BLOCKS_CONVERT, 765 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 1 }, 766 .nr_exp_ext = 1, 767 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 768 .ex_len = EXT_DATA_LEN, 769 .is_unwrit = 0 } }, 770 .is_zeroout_test = 1, 771 .nr_exp_data_segs = 2, 772 .exp_data_state = { { .exp_char = 0, .off_blk = 0, .len_blk = 1 }, 773 { .exp_char = 'X', 774 .off_blk = 1, 775 .len_blk = EXT_DATA_LEN - 1 } } }, 776 { .desc = "split unwrit extent to 3 extents and convert 2nd half writ (endio, zeroout)", 777 .type = TEST_CREATE_BLOCKS, 778 .is_unwrit_at_start = 1, 779 .split_flags = EXT4_GET_BLOCKS_CONVERT, 780 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 2 }, 781 .nr_exp_ext = 1, 782 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 783 .ex_len = EXT_DATA_LEN, 784 .is_unwrit = 0 } }, 785 .is_zeroout_test = 1, 786 .nr_exp_data_segs = 3, 787 .exp_data_state = { { .exp_char = 0, .off_blk = 0, .len_blk = 1 }, 788 { .exp_char = 'X', 789 .off_blk = 1, 790 .len_blk = EXT_DATA_LEN - 2 }, 791 { .exp_char = 0, 792 .off_blk = EXT_DATA_LEN - 1, 793 .len_blk = 1 } } }, 794 795 /* unwrit to writ splits (non-endio)*/ 796 { .desc = "split unwrit extent to 2 extents and convert 1st half writ (non-endio, zeroout)", 797 .type = TEST_CREATE_BLOCKS, 798 .is_unwrit_at_start = 1, 799 .split_flags = EXT4_GET_BLOCKS_CREATE, 800 .split_map = { .m_lblk = EXT_DATA_LBLK, .m_len = 1 }, 801 .nr_exp_ext = 1, 802 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 803 .ex_len = EXT_DATA_LEN, 804 .is_unwrit = 0 } }, 805 .is_zeroout_test = 1, 806 .nr_exp_data_segs = 2, 807 .exp_data_state = { { .exp_char = 'X', .off_blk = 0, .len_blk = 1 }, 808 { .exp_char = 0, 809 .off_blk = 1, 810 .len_blk = EXT_DATA_LEN - 1 } } }, 811 { .desc = "split unwrit extent to 2 extents and convert 2nd half writ (non-endio, zeroout)", 812 .type = TEST_CREATE_BLOCKS, 813 .is_unwrit_at_start = 1, 814 .split_flags = EXT4_GET_BLOCKS_CREATE, 815 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 1 }, 816 .nr_exp_ext = 1, 817 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 818 .ex_len = EXT_DATA_LEN, 819 .is_unwrit = 0 } }, 820 .is_zeroout_test = 1, 821 .nr_exp_data_segs = 2, 822 .exp_data_state = { { .exp_char = 0, .off_blk = 0, .len_blk = 1 }, 823 { .exp_char = 'X', 824 .off_blk = 1, 825 .len_blk = EXT_DATA_LEN - 1 } } }, 826 { .desc = "split unwrit extent to 3 extents and convert 2nd half writ (non-endio, zeroout)", 827 .type = TEST_CREATE_BLOCKS, 828 .is_unwrit_at_start = 1, 829 .split_flags = EXT4_GET_BLOCKS_CREATE, 830 .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 2 }, 831 .nr_exp_ext = 1, 832 .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK, 833 .ex_len = EXT_DATA_LEN, 834 .is_unwrit = 0 } }, 835 .is_zeroout_test = 1, 836 .nr_exp_data_segs = 3, 837 .exp_data_state = { { .exp_char = 0, .off_blk = 0, .len_blk = 1 }, 838 { .exp_char = 'X', 839 .off_blk = 1, 840 .len_blk = EXT_DATA_LEN - 2 }, 841 { .exp_char = 0, 842 .off_blk = EXT_DATA_LEN - 1, 843 .len_blk = 1 } } }, 844 }; 845 846 static void ext_get_desc(struct kunit *test, const void *p, char *desc) 847 848 { 849 struct kunit_ext_test_param *param = (struct kunit_ext_test_param *)p; 850 851 snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s %s\n", param->desc, 852 (param->type & TEST_CREATE_BLOCKS) ? "(highlevel)" : ""); 853 } 854 855 static int test_split_convert_param_init(struct kunit *test) 856 { 857 size_t arr_size = ARRAY_SIZE(test_split_convert_params); 858 859 kunit_register_params_array(test, test_split_convert_params, arr_size, 860 ext_get_desc); 861 return 0; 862 } 863 864 static int test_convert_initialized_param_init(struct kunit *test) 865 { 866 size_t arr_size = ARRAY_SIZE(test_convert_initialized_params); 867 868 kunit_register_params_array(test, test_convert_initialized_params, 869 arr_size, ext_get_desc); 870 return 0; 871 } 872 873 static int test_handle_unwritten_init(struct kunit *test) 874 { 875 size_t arr_size = ARRAY_SIZE(test_handle_unwritten_params); 876 877 kunit_register_params_array(test, test_handle_unwritten_params, 878 arr_size, ext_get_desc); 879 return 0; 880 } 881 882 /* 883 * Note that we use KUNIT_CASE_PARAM_WITH_INIT() instead of the more compact 884 * KUNIT_ARRAY_PARAM() because the later currently has a limitation causing the 885 * output parsing to be prone to error. For more context: 886 * 887 * https://lore.kernel.org/linux-kselftest/aULJpTvJDw9ctUDe@li-dc0c254c-257c-11b2-a85c-98b6c1322444.ibm.com/ 888 */ 889 static struct kunit_case extents_test_cases[] = { 890 KUNIT_CASE_PARAM_WITH_INIT(test_split_convert, kunit_array_gen_params, 891 test_split_convert_param_init, NULL), 892 KUNIT_CASE_PARAM_WITH_INIT(test_split_convert, kunit_array_gen_params, 893 test_convert_initialized_param_init, NULL), 894 KUNIT_CASE_PARAM_WITH_INIT(test_split_convert, kunit_array_gen_params, 895 test_handle_unwritten_init, NULL), 896 {} 897 }; 898 899 static struct kunit_suite extents_test_suite = { 900 .name = "ext4_extents_test", 901 .init = extents_kunit_init, 902 .exit = extents_kunit_exit, 903 .test_cases = extents_test_cases, 904 }; 905 906 kunit_test_suites(&extents_test_suite); 907 908 MODULE_LICENSE("GPL"); 909