| 1 | |
| 2 | |
| 3 | * An API to allow a function, that may fail, to be executed, and recover in a
|
| 4 | * controlled manner.
|
| 5 | *
|
| 6 | * Copyright (C) 2019, Google LLC.
|
| 7 | * Author: Brendan Higgins <[email protected]>
|
| 8 |
|
| 9 |
|
| 10 | #include <kunit/test.h>
|
| 11 | #include <linux/completion.h>
|
| 12 | #include <linux/kernel.h>
|
| 13 | #include <linux/kthread.h>
|
| 14 | #include <linux/sched/task.h>
|
| 15 |
|
| 16 | #include "try-catch-impl.h"
|
| 17 |
|
| 18 | void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch)
|
| 19 | {
|
| 20 | try_catch->try_result = -EFAULT;
|
| 21 | kthread_exit(0);
|
| 22 | }
|
| 23 | EXPORT_SYMBOL_GPL(kunit_try_catch_throw);
|
| 24 |
|
| 25 | static int kunit_generic_run_threadfn_adapter(void *data)
|
| 26 | {
|
| 27 | struct kunit_try_catch *try_catch = data;
|
| 28 |
|
| 29 | try_catch->try_result = -EINTR;
|
| 30 | try_catch->try(try_catch->context);
|
| 31 | if (try_catch->try_result == -EINTR)
|
| 32 | try_catch->try_result = 0;
|
| 33 |
|
| 34 | return 0;
|
| 35 | }
|
| 36 |
|
| 37 | void kunit_try_catch_run(struct kunit_try_catch *try_catch, void *context)
|
| 38 | {
|
| 39 | struct kunit *test = try_catch->test;
|
| 40 | struct task_struct *task_struct;
|
| 41 | struct completion *task_done;
|
| 42 | int exit_code, time_remaining;
|
| 43 |
|
| 44 | try_catch->context = context;
|
| 45 | try_catch->try_result = 0;
|
| 46 | task_struct = kthread_create(kunit_generic_run_threadfn_adapter,
|
| 47 | try_catch, "kunit_try_catch_thread");
|
| 48 | if (IS_ERR(task_struct)) {
|
| 49 | try_catch->try_result = PTR_ERR(task_struct);
|
| 50 | try_catch->catch(try_catch->context);
|
| 51 | return;
|
| 52 | }
|
| 53 | get_task_struct(task_struct);
|
| 54 | |
| 55 | * As for a vfork(2), task_struct->vfork_done (pointing to the
|
| 56 | * underlying kthread->exited) can be used to wait for the end of a
|
| 57 | * kernel thread. It is set to NULL when the thread exits, so we
|
| 58 | * keep a copy here.
|
| 59 |
|
| 60 | task_done = task_struct->vfork_done;
|
| 61 | wake_up_process(task_struct);
|
| 62 |
|
| 63 | time_remaining = wait_for_completion_timeout(
|
| 64 | task_done, try_catch->timeout);
|
| 65 | if (time_remaining == 0) {
|
| 66 | try_catch->try_result = -ETIMEDOUT;
|
| 67 | kthread_stop(task_struct);
|
| 68 | }
|
| 69 |
|
| 70 | put_task_struct(task_struct);
|
| 71 | exit_code = try_catch->try_result;
|
| 72 |
|
| 73 | if (!exit_code)
|
| 74 | return;
|
| 75 |
|
| 76 | if (exit_code == -EFAULT)
|
| 77 | try_catch->try_result = 0;
|
| 78 | else if (exit_code == -EINTR) {
|
| 79 | if (test->last_seen.file)
|
| 80 | kunit_err(test, "try faulted: last line seen %s:%d\n",
|
| 81 | test->last_seen.file, test->last_seen.line);
|
| 82 | else
|
| 83 | kunit_err(test, "try faulted\n");
|
| 84 | } else if (exit_code == -ETIMEDOUT)
|
| 85 | kunit_err(test, "try timed out\n");
|
| 86 | else if (exit_code)
|
| 87 | kunit_err(test, "Unknown error: %d\n", exit_code);
|
| 88 |
|
| 89 | try_catch->catch(try_catch->context);
|
| 90 | }
|
| 91 | EXPORT_SYMBOL_GPL(kunit_try_catch_run);
|