1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (C) 2025 Igalia S.L. 4 * 5 * Robust list test by André Almeida <andrealmeid@igalia.com> 6 * 7 * The robust list uAPI allows userspace to create "robust" locks, in the sense 8 * that if the lock holder thread dies, the remaining threads that are waiting 9 * for the lock won't block forever, waiting for a lock that will never be 10 * released. 11 * 12 * This is achieve by userspace setting a list where a thread can enter all the 13 * locks (futexes) that it is holding. The robust list is a linked list, and 14 * userspace register the start of the list with the syscall set_robust_list(). 15 * If such thread eventually dies, the kernel will walk this list, waking up one 16 * thread waiting for each futex and marking the futex word with the flag 17 * FUTEX_OWNER_DIED. 18 * 19 * See also 20 * man set_robust_list 21 * Documententation/locking/robust-futex-ABI.rst 22 * Documententation/locking/robust-futexes.rst 23 */ 24 25 #define _GNU_SOURCE 26 27 #include "futextest.h" 28 #include "../../kselftest_harness.h" 29 30 #include <dlfcn.h> 31 #include <errno.h> 32 #include <pthread.h> 33 #include <signal.h> 34 #include <stdint.h> 35 #include <stdatomic.h> 36 #include <stdbool.h> 37 #include <stddef.h> 38 #include <sys/auxv.h> 39 #include <sys/mman.h> 40 #include <sys/wait.h> 41 42 #define STACK_SIZE (1024 * 1024) 43 44 #define FUTEX_TIMEOUT 3 45 46 #define SLEEP_US 100 47 48 #if __SIZEOF_LONG__ == 8 49 # define BUILD_64 50 #endif 51 52 static pthread_barrier_t barrier, barrier2; 53 54 static int set_robust_list(struct robust_list_head *head, size_t len) 55 { 56 return syscall(SYS_set_robust_list, head, len); 57 } 58 59 static int get_robust_list(int pid, struct robust_list_head **head, size_t *len_ptr) 60 { 61 return syscall(SYS_get_robust_list, pid, head, len_ptr); 62 } 63 64 static int sys_futex_robust_unlock(_Atomic(uint32_t) *uaddr, unsigned int op, int val, 65 void *list_op_pending, unsigned int val3) 66 { 67 return syscall(SYS_futex, uaddr, op, val, NULL, list_op_pending, val3, 0); 68 } 69 70 /* 71 * Basic lock struct, contains just the futex word and the robust list element 72 * Real implementations have also a *prev to easily walk in the list 73 */ 74 struct lock_struct { 75 _Atomic(unsigned int) futex; 76 struct robust_list list; 77 }; 78 79 /* 80 * Helper function to spawn a child thread. Returns -1 on error, pid on success 81 */ 82 static int create_child(int (*fn)(void *arg), void *arg) 83 { 84 char *stack; 85 pid_t pid; 86 87 stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, 88 MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); 89 if (stack == MAP_FAILED) 90 return -1; 91 92 stack += STACK_SIZE; 93 94 pid = clone(fn, stack, CLONE_VM | SIGCHLD, arg); 95 96 if (pid == -1) 97 return -1; 98 99 return pid; 100 } 101 102 /* 103 * Helper function to prepare and register a robust list 104 */ 105 static int set_list(struct robust_list_head *head) 106 { 107 int ret; 108 109 ret = set_robust_list(head, sizeof(*head)); 110 if (ret) 111 return ret; 112 113 head->futex_offset = (size_t) offsetof(struct lock_struct, futex) - 114 (size_t) offsetof(struct lock_struct, list); 115 head->list.next = &head->list; 116 head->list_op_pending = NULL; 117 118 return 0; 119 } 120 121 /* 122 * A basic (and incomplete) mutex lock function with robustness 123 */ 124 static int mutex_lock(struct lock_struct *lock, struct robust_list_head *head, bool error_inject) 125 { 126 _Atomic(unsigned int) *futex = &lock->futex; 127 unsigned int zero = 0; 128 pid_t tid = gettid(); 129 int ret = -1; 130 131 /* 132 * Set list_op_pending before starting the lock, so the kernel can catch 133 * the case where the thread died during the lock operation 134 */ 135 head->list_op_pending = &lock->list; 136 137 if (atomic_compare_exchange_strong(futex, &zero, tid)) { 138 /* 139 * We took the lock, insert it in the robust list 140 */ 141 struct robust_list *list = &head->list; 142 143 /* Error injection to test list_op_pending */ 144 if (error_inject) 145 return 0; 146 147 while (list->next != &head->list) 148 list = list->next; 149 150 list->next = &lock->list; 151 lock->list.next = &head->list; 152 153 ret = 0; 154 } else { 155 /* 156 * We didn't take the lock, wait until the owner wakes (or dies) 157 */ 158 struct timespec to; 159 160 to.tv_sec = FUTEX_TIMEOUT; 161 to.tv_nsec = 0; 162 163 tid = atomic_load(futex); 164 /* Kernel ignores futexes without the waiters flag */ 165 tid |= FUTEX_WAITERS; 166 atomic_store(futex, tid); 167 168 ret = futex_wait((futex_t *) futex, tid, &to, 0); 169 170 /* 171 * A real mutex_lock() implementation would loop here to finally 172 * take the lock. We don't care about that, so we stop here. 173 */ 174 } 175 176 head->list_op_pending = NULL; 177 178 return ret; 179 } 180 181 /* 182 * This child thread will succeed taking the lock, and then will exit holding it 183 */ 184 static int child_fn_lock(void *arg) 185 { 186 struct lock_struct *lock = arg; 187 struct robust_list_head head; 188 int ret; 189 190 ret = set_list(&head); 191 if (ret) { 192 ksft_test_result_fail("set_robust_list error\n"); 193 return ret; 194 } 195 196 ret = mutex_lock(lock, &head, false); 197 if (ret) { 198 ksft_test_result_fail("mutex_lock error\n"); 199 return ret; 200 } 201 202 pthread_barrier_wait(&barrier); 203 204 /* 205 * There's a race here: the parent thread needs to be inside 206 * futex_wait() before the child thread dies, otherwise it will miss the 207 * wakeup from handle_futex_death() that this child will emit. We wait a 208 * little bit just to make sure that this happens. 209 */ 210 usleep(SLEEP_US); 211 212 return 0; 213 } 214 215 /* 216 * Spawns a child thread that will set a robust list, take the lock, register it 217 * in the robust list and die. The parent thread will wait on this futex, and 218 * should be waken up when the child exits. 219 */ 220 TEST(test_robustness) 221 { 222 struct lock_struct lock = { .futex = 0 }; 223 _Atomic(unsigned int) *futex = &lock.futex; 224 struct robust_list_head head; 225 int ret, pid, wstatus; 226 227 ret = set_list(&head); 228 ASSERT_EQ(ret, 0); 229 230 /* 231 * Lets use a barrier to ensure that the child thread takes the lock 232 * before the parent 233 */ 234 ret = pthread_barrier_init(&barrier, NULL, 2); 235 ASSERT_EQ(ret, 0); 236 237 pid = create_child(&child_fn_lock, &lock); 238 ASSERT_NE(pid, -1); 239 240 pthread_barrier_wait(&barrier); 241 ret = mutex_lock(&lock, &head, false); 242 243 /* 244 * futex_wait() should return 0 and the futex word should be marked with 245 * FUTEX_OWNER_DIED 246 */ 247 ASSERT_EQ(ret, 0); 248 249 ASSERT_TRUE(*futex & FUTEX_OWNER_DIED); 250 251 wait(&wstatus); 252 pthread_barrier_destroy(&barrier); 253 254 /* Pass only if the child hasn't return error */ 255 if (!WEXITSTATUS(wstatus)) 256 ksft_test_result_pass("%s\n", __func__); 257 } 258 259 /* 260 * The only valid value for len is sizeof(*head) 261 */ 262 TEST(test_set_robust_list_invalid_size) 263 { 264 struct robust_list_head head; 265 size_t head_size = sizeof(head); 266 int ret; 267 268 ret = set_robust_list(&head, head_size); 269 ASSERT_EQ(ret, 0); 270 271 ret = set_robust_list(&head, head_size * 2); 272 ASSERT_EQ(ret, -1); 273 ASSERT_EQ(errno, EINVAL); 274 275 ret = set_robust_list(&head, head_size - 1); 276 ASSERT_EQ(ret, -1); 277 ASSERT_EQ(errno, EINVAL); 278 279 ret = set_robust_list(&head, 0); 280 ASSERT_EQ(ret, -1); 281 ASSERT_EQ(errno, EINVAL); 282 283 ksft_test_result_pass("%s\n", __func__); 284 } 285 286 /* 287 * Test get_robust_list with pid = 0, getting the list of the running thread 288 */ 289 TEST(test_get_robust_list_self) 290 { 291 struct robust_list_head head, head2, *get_head; 292 size_t head_size = sizeof(head), len_ptr; 293 int ret; 294 295 ret = set_robust_list(&head, head_size); 296 ASSERT_EQ(ret, 0); 297 298 ret = get_robust_list(0, &get_head, &len_ptr); 299 ASSERT_EQ(ret, 0); 300 ASSERT_EQ(get_head, &head); 301 ASSERT_EQ(head_size, len_ptr); 302 303 ret = set_robust_list(&head2, head_size); 304 ASSERT_EQ(ret, 0); 305 306 ret = get_robust_list(0, &get_head, &len_ptr); 307 ASSERT_EQ(ret, 0); 308 ASSERT_EQ(get_head, &head2); 309 ASSERT_EQ(head_size, len_ptr); 310 311 ksft_test_result_pass("%s\n", __func__); 312 } 313 314 static int child_list(void *arg) 315 { 316 struct robust_list_head *head = arg; 317 int ret; 318 319 ret = set_robust_list(head, sizeof(*head)); 320 if (ret) { 321 ksft_test_result_fail("set_robust_list error\n"); 322 return -1; 323 } 324 325 /* 326 * After setting the list head, wait until the main thread can call 327 * get_robust_list() for this thread before exiting. 328 */ 329 pthread_barrier_wait(&barrier); 330 pthread_barrier_wait(&barrier2); 331 332 return 0; 333 } 334 335 /* 336 * Test get_robust_list from another thread. We use two barriers here to ensure 337 * that: 338 * 1) the child thread set the list before we try to get it from the 339 * parent 340 * 2) the child thread still alive when we try to get the list from it 341 */ 342 TEST(test_get_robust_list_child) 343 { 344 struct robust_list_head head, *get_head; 345 int ret, wstatus; 346 size_t len_ptr; 347 pid_t tid; 348 349 ret = pthread_barrier_init(&barrier, NULL, 2); 350 ret = pthread_barrier_init(&barrier2, NULL, 2); 351 ASSERT_EQ(ret, 0); 352 353 tid = create_child(&child_list, &head); 354 ASSERT_NE(tid, -1); 355 356 pthread_barrier_wait(&barrier); 357 358 ret = get_robust_list(tid, &get_head, &len_ptr); 359 ASSERT_EQ(ret, 0); 360 ASSERT_EQ(&head, get_head); 361 362 pthread_barrier_wait(&barrier2); 363 364 wait(&wstatus); 365 pthread_barrier_destroy(&barrier); 366 pthread_barrier_destroy(&barrier2); 367 368 /* Pass only if the child hasn't return error */ 369 if (!WEXITSTATUS(wstatus)) 370 ksft_test_result_pass("%s\n", __func__); 371 } 372 373 static int child_fn_lock_with_error(void *arg) 374 { 375 struct lock_struct *lock = arg; 376 struct robust_list_head head; 377 int ret; 378 379 ret = set_list(&head); 380 if (ret) { 381 ksft_test_result_fail("set_robust_list error\n"); 382 return -1; 383 } 384 385 ret = mutex_lock(lock, &head, true); 386 if (ret) { 387 ksft_test_result_fail("mutex_lock error\n"); 388 return -1; 389 } 390 391 pthread_barrier_wait(&barrier); 392 393 /* See comment at child_fn_lock() */ 394 usleep(SLEEP_US); 395 396 return 0; 397 } 398 399 /* 400 * Same as robustness test, but inject an error where the mutex_lock() exits 401 * earlier, just after setting list_op_pending and taking the lock, to test the 402 * list_op_pending mechanism 403 */ 404 TEST(test_set_list_op_pending) 405 { 406 struct lock_struct lock = { .futex = 0 }; 407 _Atomic(unsigned int) *futex = &lock.futex; 408 struct robust_list_head head; 409 int ret, wstatus; 410 411 ret = set_list(&head); 412 ASSERT_EQ(ret, 0); 413 414 ret = pthread_barrier_init(&barrier, NULL, 2); 415 ASSERT_EQ(ret, 0); 416 417 ret = create_child(&child_fn_lock_with_error, &lock); 418 ASSERT_NE(ret, -1); 419 420 pthread_barrier_wait(&barrier); 421 ret = mutex_lock(&lock, &head, false); 422 423 ASSERT_EQ(ret, 0); 424 425 ASSERT_TRUE(*futex & FUTEX_OWNER_DIED); 426 427 wait(&wstatus); 428 pthread_barrier_destroy(&barrier); 429 430 /* Pass only if the child hasn't return error */ 431 if (!WEXITSTATUS(wstatus)) 432 ksft_test_result_pass("%s\n", __func__); 433 else 434 ksft_test_result_fail("%s\n", __func__); 435 } 436 437 #define CHILD_NR 10 438 439 static int child_lock_holder(void *arg) 440 { 441 struct lock_struct *locks = arg; 442 struct robust_list_head head; 443 int i; 444 445 set_list(&head); 446 447 for (i = 0; i < CHILD_NR; i++) { 448 locks[i].futex = 0; 449 mutex_lock(&locks[i], &head, false); 450 } 451 452 pthread_barrier_wait(&barrier); 453 pthread_barrier_wait(&barrier2); 454 455 /* See comment at child_fn_lock() */ 456 usleep(SLEEP_US); 457 458 return 0; 459 } 460 461 static int child_wait_lock(void *arg) 462 { 463 struct lock_struct *lock = arg; 464 struct robust_list_head head; 465 int ret; 466 467 pthread_barrier_wait(&barrier2); 468 ret = mutex_lock(lock, &head, false); 469 470 if (ret) { 471 ksft_test_result_fail("mutex_lock error\n"); 472 return -1; 473 } 474 475 if (!(lock->futex & FUTEX_OWNER_DIED)) { 476 ksft_test_result_fail("futex not marked with FUTEX_OWNER_DIED\n"); 477 return -1; 478 } 479 480 return 0; 481 } 482 483 /* 484 * Test a robust list of more than one element. All the waiters should wake when 485 * the holder dies 486 */ 487 TEST(test_robust_list_multiple_elements) 488 { 489 struct lock_struct locks[CHILD_NR]; 490 pid_t pids[CHILD_NR + 1]; 491 int i, ret, wstatus; 492 493 ret = pthread_barrier_init(&barrier, NULL, 2); 494 ASSERT_EQ(ret, 0); 495 ret = pthread_barrier_init(&barrier2, NULL, CHILD_NR + 1); 496 ASSERT_EQ(ret, 0); 497 498 pids[0] = create_child(&child_lock_holder, &locks); 499 500 /* Wait until the locker thread takes the look */ 501 pthread_barrier_wait(&barrier); 502 503 for (i = 0; i < CHILD_NR; i++) 504 pids[i+1] = create_child(&child_wait_lock, &locks[i]); 505 506 /* Wait for all children to return */ 507 ret = 0; 508 509 for (i = 0; i < CHILD_NR; i++) { 510 waitpid(pids[i], &wstatus, 0); 511 if (WEXITSTATUS(wstatus)) 512 ret = -1; 513 } 514 515 pthread_barrier_destroy(&barrier); 516 pthread_barrier_destroy(&barrier2); 517 518 /* Pass only if the child hasn't return error */ 519 if (!ret) 520 ksft_test_result_pass("%s\n", __func__); 521 } 522 523 static int child_circular_list(void *arg) 524 { 525 static struct robust_list_head head; 526 struct lock_struct a, b, c; 527 int ret; 528 529 ret = set_list(&head); 530 if (ret) { 531 ksft_test_result_fail("set_list error\n"); 532 return -1; 533 } 534 535 head.list.next = &a.list; 536 537 /* 538 * The last element should point to head list, but we short circuit it 539 */ 540 a.list.next = &b.list; 541 b.list.next = &c.list; 542 c.list.next = &a.list; 543 544 return 0; 545 } 546 547 /* 548 * Create a circular robust list. The kernel should be able to destroy the list 549 * while processing it so it won't be trapped in an infinite loop while handling 550 * a process exit 551 */ 552 TEST(test_circular_list) 553 { 554 int wstatus; 555 556 create_child(child_circular_list, NULL); 557 558 wait(&wstatus); 559 560 /* Pass only if the child hasn't return error */ 561 if (!WEXITSTATUS(wstatus)) 562 ksft_test_result_pass("%s\n", __func__); 563 } 564 565 /* 566 * Below are tests for the fix of robust release race condition. Please read the following 567 * thread to learn more about the issue in the first place and why the following functions fix it: 568 * https://lore.kernel.org/lkml/20260316162316.356674433@kernel.org/ 569 */ 570 571 /* 572 * Auxiliary code for binding the vDSO functions 573 */ 574 static void *get_vdso_func_addr(const char *function) 575 { 576 const char *vdso_names[] = { 577 "linux-vdso.so.1", "linux-gate.so.1", "linux-vdso32.so.1", "linux-vdso64.so.1", 578 }; 579 580 for (int i = 0; i < ARRAY_SIZE(vdso_names); i++) { 581 void *vdso = dlopen(vdso_names[i], RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); 582 583 if (vdso) 584 return dlsym(vdso, function); 585 } 586 return NULL; 587 } 588 589 /* 590 * These are the real vDSO function signatures: 591 * 592 * __vdso_futex_robust_list64_try_unlock(__u32 *lock, __u32 tid, __u64 *pop) 593 * __vdso_futex_robust_list32_try_unlock(__u32 *lock, __u32 tid, __u32 *pop) 594 * 595 * So for the generic entry point we need to use a void pointer as the last argument 596 */ 597 FIXTURE(vdso_unlock) 598 { 599 uint32_t (*vdso)(_Atomic(uint32_t) *lock, uint32_t tid, void *pop); 600 }; 601 602 FIXTURE_VARIANT(vdso_unlock) 603 { 604 bool is_32; 605 char func_name[]; 606 }; 607 608 FIXTURE_SETUP(vdso_unlock) 609 { 610 self->vdso = get_vdso_func_addr(variant->func_name); 611 } 612 613 FIXTURE_TEARDOWN(vdso_unlock) {} 614 615 FIXTURE_VARIANT_ADD(vdso_unlock, 32) 616 { 617 .func_name = "__vdso_futex_robust_list32_try_unlock", 618 .is_32 = true, 619 }; 620 621 FIXTURE_VARIANT_ADD(vdso_unlock, 64) 622 { 623 .func_name = "__vdso_futex_robust_list64_try_unlock", 624 .is_32 = false, 625 }; 626 627 /* 628 * Test the vDSO robust_listXX_try_unlock() for the uncontended case. The virtual syscall should 629 * return the thread ID of the lock owner, the lock word must be 0 and the list_op_pending should 630 * be NULL. 631 */ 632 TEST_F(vdso_unlock, test_robust_try_unlock_uncontended) 633 { 634 struct lock_struct lock = { .futex = 0 }; 635 _Atomic(unsigned int) *futex = &lock.futex; 636 struct robust_list_head head; 637 uintptr_t exp = (uintptr_t) NULL; 638 pid_t tid = gettid(); 639 int ret; 640 641 if (!self->vdso) { 642 ksft_test_result_skip("%s not found\n", variant->func_name); 643 return; 644 } 645 646 *futex = tid; 647 648 ret = set_list(&head); 649 if (ret) 650 ksft_test_result_fail("set_robust_list error\n"); 651 652 head.list_op_pending = &lock.list; 653 654 ret = self->vdso(futex, tid, &head.list_op_pending); 655 656 ASSERT_EQ(ret, tid); 657 ASSERT_EQ(*futex, 0); 658 659 /* Check only the lower 32 bits for the 32-bit entry point */ 660 if (variant->is_32) { 661 exp = (uintptr_t)(unsigned long)&lock.list; 662 exp &= ~0xFFFFFFFFULL; 663 } 664 665 ASSERT_EQ((uintptr_t)(unsigned long)head.list_op_pending, exp); 666 } 667 668 /* 669 * If the lock is contended, the operation fails. The return value is the value found at the 670 * futex word (tid | FUTEX_WAITERS), the futex word is not modified and the list_op_pending is_32 671 * not cleared. 672 */ 673 TEST_F(vdso_unlock, test_robust_try_unlock_contended) 674 { 675 struct lock_struct lock = { .futex = 0 }; 676 _Atomic(unsigned int) *futex = &lock.futex; 677 struct robust_list_head head; 678 pid_t tid = gettid(); 679 int ret; 680 681 if (!self->vdso) { 682 ksft_test_result_skip("%s not found\n", variant->func_name); 683 return; 684 } 685 686 *futex = tid | FUTEX_WAITERS; 687 688 ret = set_list(&head); 689 if (ret) 690 ksft_test_result_fail("set_robust_list error\n"); 691 692 head.list_op_pending = &lock.list; 693 694 ret = self->vdso(futex, tid, &head.list_op_pending); 695 696 ASSERT_EQ(ret, tid | FUTEX_WAITERS); 697 ASSERT_EQ(*futex, tid | FUTEX_WAITERS); 698 ASSERT_EQ(head.list_op_pending, &lock.list); 699 } 700 701 FIXTURE(futex_op) {}; 702 703 FIXTURE_VARIANT(futex_op) 704 { 705 unsigned int op; 706 unsigned int val3; 707 }; 708 709 FIXTURE_SETUP(futex_op) {} 710 711 FIXTURE_TEARDOWN(futex_op) {} 712 713 FIXTURE_VARIANT_ADD(futex_op, wake) 714 { 715 .op = FUTEX_WAKE, 716 .val3 = 0, 717 }; 718 719 FIXTURE_VARIANT_ADD(futex_op, wake_bitset) 720 { 721 .op = FUTEX_WAKE_BITSET, 722 .val3 = FUTEX_BITSET_MATCH_ANY, 723 }; 724 725 FIXTURE_VARIANT_ADD(futex_op, unlock_pi) 726 { 727 .op = FUTEX_UNLOCK_PI, 728 .val3 = 0, 729 }; 730 731 FIXTURE_VARIANT_ADD(futex_op, wake32) 732 { 733 .op = FUTEX_WAKE | FUTEX_ROBUST_LIST32, 734 .val3 = 0, 735 }; 736 737 FIXTURE_VARIANT_ADD(futex_op, wake_bitset32) 738 { 739 .op = FUTEX_WAKE_BITSET | FUTEX_ROBUST_LIST32, 740 .val3 = FUTEX_BITSET_MATCH_ANY, 741 }; 742 743 FIXTURE_VARIANT_ADD(futex_op, unlock_pi32) 744 { 745 .op = FUTEX_UNLOCK_PI | FUTEX_ROBUST_LIST32, 746 .val3 = 0, 747 }; 748 749 /* 750 * The syscall should return the number of tasks waken (for this test, 0), clear the futex word and 751 * clear list_op_pending 752 */ 753 TEST_F(futex_op, test_futex_robust_unlock) 754 { 755 struct lock_struct lock = { .futex = 0 }; 756 _Atomic(unsigned int) *futex = &lock.futex; 757 uintptr_t exp = (uintptr_t) NULL; 758 struct robust_list_head head; 759 pid_t tid = gettid(); 760 int ret; 761 762 #ifndef BUILD_64 763 if (!(variant->op & FUTEX_ROBUST_LIST32)) { 764 ksft_test_result_skip("Not supported for 32 bit build\n"); 765 return; 766 } 767 #endif 768 769 *futex = tid | FUTEX_WAITERS; 770 771 ret = set_list(&head); 772 if (ret) 773 ksft_test_result_fail("set_robust_list error\n"); 774 775 head.list_op_pending = &lock.list; 776 777 ret = sys_futex_robust_unlock(futex, FUTEX_ROBUST_UNLOCK | variant->op, tid, 778 &head.list_op_pending, variant->val3); 779 780 ASSERT_EQ(ret, 0); 781 ASSERT_EQ(*futex, 0); 782 783 if (variant->op & FUTEX_ROBUST_LIST32) { 784 exp = (uint64_t)(unsigned long)&lock.list; 785 exp &= ~0xFFFFFFFFULL; 786 } 787 788 ASSERT_EQ((uintptr_t)(unsigned long)head.list_op_pending, exp); 789 } 790 791 TEST_HARNESS_MAIN 792