xref: /linux/tools/testing/selftests/futex/functional/robust_list.c (revision 608323bf7bb85bbb647eca4373acef247f105e67)
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