// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include #include #include #include #include "../../../../../include/linux/kernel.h" #include "aolib.h" static char ftrace_path[] = "ksft-ftrace-XXXXXX"; static bool ftrace_mounted; uint64_t ns_cookie1, ns_cookie2; struct test_ftracer { pthread_t tracer_thread; int error; char *instance_path; FILE *trace_pipe; enum ftracer_op (*process_line)(const char *line); void (*destructor)(struct test_ftracer *tracer); bool (*expecting_more)(void); char **saved_lines; size_t saved_lines_size; size_t next_line_ind; pthread_cond_t met_all_expected; pthread_mutex_t met_all_expected_lock; struct test_ftracer *next; }; static struct test_ftracer *ftracers; static pthread_mutex_t ftracers_lock = PTHREAD_MUTEX_INITIALIZER; static int mount_ftrace(void) { if (!mkdtemp(ftrace_path)) test_error("Can't create temp dir"); if (mount("tracefs", ftrace_path, "tracefs", 0, "rw")) return -errno; ftrace_mounted = true; return 0; } static void unmount_ftrace(void) { if (ftrace_mounted && umount(ftrace_path)) test_print("Failed on cleanup: can't unmount tracefs: %m"); if (rmdir(ftrace_path)) test_error("Failed on cleanup: can't remove ftrace dir %s", ftrace_path); } struct opts_list_t { char *opt_name; struct opts_list_t *next; }; static int disable_trace_options(const char *ftrace_path) { struct opts_list_t *opts_list = NULL; char *fopts, *line = NULL; size_t buf_len = 0; ssize_t line_len; int ret = 0; FILE *opts; fopts = test_sprintf("%s/%s", ftrace_path, "trace_options"); if (!fopts) return -ENOMEM; opts = fopen(fopts, "r+"); if (!opts) { ret = -errno; goto out_free; } while ((line_len = getline(&line, &buf_len, opts)) != -1) { struct opts_list_t *tmp; if (!strncmp(line, "no", 2)) continue; tmp = malloc(sizeof(*tmp)); if (!tmp) { ret = -ENOMEM; goto out_free_opts_list; } tmp->next = opts_list; tmp->opt_name = test_sprintf("no%s", line); if (!tmp->opt_name) { ret = -ENOMEM; free(tmp); goto out_free_opts_list; } opts_list = tmp; } while (opts_list) { struct opts_list_t *tmp = opts_list; fseek(opts, 0, SEEK_SET); fwrite(tmp->opt_name, 1, strlen(tmp->opt_name), opts); opts_list = opts_list->next; free(tmp->opt_name); free(tmp); } out_free_opts_list: while (opts_list) { struct opts_list_t *tmp = opts_list; opts_list = opts_list->next; free(tmp->opt_name); free(tmp); } free(line); fclose(opts); out_free: free(fopts); return ret; } static int setup_buffer_size(const char *ftrace_path, size_t sz) { char *fbuf_size = test_sprintf("%s/buffer_size_kb", ftrace_path); int ret; if (!fbuf_size) return -1; ret = test_echo(fbuf_size, 0, "%zu", sz); free(fbuf_size); return ret; } static int setup_ftrace_instance(struct test_ftracer *tracer, const char *name) { char *tmp; tmp = test_sprintf("%s/instances/ksft-%s-XXXXXX", ftrace_path, name); if (!tmp) return -ENOMEM; tracer->instance_path = mkdtemp(tmp); if (!tracer->instance_path) { free(tmp); return -errno; } return 0; } static void remove_ftrace_instance(struct test_ftracer *tracer) { if (rmdir(tracer->instance_path)) test_print("Failed on cleanup: can't remove ftrace instance %s", tracer->instance_path); free(tracer->instance_path); } static void tracer_cleanup(void *arg) { struct test_ftracer *tracer = arg; fclose(tracer->trace_pipe); } static void tracer_set_error(struct test_ftracer *tracer, int error) { if (!tracer->error) tracer->error = error; } const size_t tracer_get_savedlines_nr(struct test_ftracer *tracer) { return tracer->next_line_ind; } const char **tracer_get_savedlines(struct test_ftracer *tracer) { return (const char **)tracer->saved_lines; } static void *tracer_thread_func(void *arg) { struct test_ftracer *tracer = arg; pthread_cleanup_push(tracer_cleanup, arg); while (tracer->next_line_ind < tracer->saved_lines_size) { char **lp = &tracer->saved_lines[tracer->next_line_ind]; enum ftracer_op op; size_t buf_len = 0; ssize_t line_len; line_len = getline(lp, &buf_len, tracer->trace_pipe); if (line_len == -1) break; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); op = tracer->process_line(*lp); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); if (tracer->expecting_more) { pthread_mutex_lock(&tracer->met_all_expected_lock); if (!tracer->expecting_more()) pthread_cond_signal(&tracer->met_all_expected); pthread_mutex_unlock(&tracer->met_all_expected_lock); } if (op == FTRACER_LINE_DISCARD) continue; if (op == FTRACER_EXIT) break; if (op != FTRACER_LINE_PRESERVE) test_error("unexpected tracer command %d", op); tracer->next_line_ind++; buf_len = 0; } test_print("too many lines in ftracer buffer %zu, exiting tracer", tracer->next_line_ind); pthread_cleanup_pop(1); return NULL; } static int setup_trace_thread(struct test_ftracer *tracer) { int ret = 0; char *path; path = test_sprintf("%s/trace_pipe", tracer->instance_path); if (!path) return -ENOMEM; tracer->trace_pipe = fopen(path, "r"); if (!tracer->trace_pipe) { ret = -errno; goto out_free; } if (pthread_create(&tracer->tracer_thread, NULL, tracer_thread_func, (void *)tracer)) { ret = -errno; fclose(tracer->trace_pipe); } out_free: free(path); return ret; } static void stop_trace_thread(struct test_ftracer *tracer) { void *res; if (pthread_cancel(tracer->tracer_thread)) { test_print("Can't stop tracer pthread: %m"); tracer_set_error(tracer, -errno); } if (pthread_join(tracer->tracer_thread, &res)) { test_print("Can't join tracer pthread: %m"); tracer_set_error(tracer, -errno); } if (res != PTHREAD_CANCELED) { test_print("Tracer thread wasn't canceled"); tracer_set_error(tracer, -errno); } if (tracer->error) test_fail("tracer errored by %s", strerror(tracer->error)); } static void final_wait_for_events(struct test_ftracer *tracer, unsigned timeout_sec) { struct timespec timeout; struct timeval now; int ret = 0; if (!tracer->expecting_more) return; pthread_mutex_lock(&tracer->met_all_expected_lock); gettimeofday(&now, NULL); timeout.tv_sec = now.tv_sec + timeout_sec; timeout.tv_nsec = now.tv_usec * 1000; while (tracer->expecting_more() && ret != ETIMEDOUT) ret = pthread_cond_timedwait(&tracer->met_all_expected, &tracer->met_all_expected_lock, &timeout); pthread_mutex_unlock(&tracer->met_all_expected_lock); } int setup_trace_event(struct test_ftracer *tracer, const char *event, const char *filter) { char *enable_path, *filter_path, *instance = tracer->instance_path; int ret; enable_path = test_sprintf("%s/events/%s/enable", instance, event); if (!enable_path) return -ENOMEM; filter_path = test_sprintf("%s/events/%s/filter", instance, event); if (!filter_path) { ret = -ENOMEM; goto out_free; } ret = test_echo(filter_path, 0, "%s", filter); if (!ret) ret = test_echo(enable_path, 0, "1"); out_free: free(filter_path); free(enable_path); return ret; } struct test_ftracer *create_ftracer(const char *name, enum ftracer_op (*process_line)(const char *line), void (*destructor)(struct test_ftracer *tracer), bool (*expecting_more)(void), size_t lines_buf_sz, size_t buffer_size_kb) { struct test_ftracer *tracer; int err; /* XXX: separate __create_ftracer() helper and do here * if (!kernel_config_has(KCONFIG_FTRACE)) * return NULL; */ tracer = malloc(sizeof(*tracer)); if (!tracer) { test_print("malloc()"); return NULL; } memset(tracer, 0, sizeof(*tracer)); err = setup_ftrace_instance(tracer, name); if (err) { test_print("setup_ftrace_instance(): %d", err); goto err_free; } err = disable_trace_options(tracer->instance_path); if (err) { test_print("disable_trace_options(): %d", err); goto err_remove; } err = setup_buffer_size(tracer->instance_path, buffer_size_kb); if (err) { test_print("disable_trace_options(): %d", err); goto err_remove; } tracer->saved_lines = calloc(lines_buf_sz, sizeof(tracer->saved_lines[0])); if (!tracer->saved_lines) { test_print("calloc()"); goto err_remove; } tracer->saved_lines_size = lines_buf_sz; tracer->process_line = process_line; tracer->destructor = destructor; tracer->expecting_more = expecting_more; err = pthread_cond_init(&tracer->met_all_expected, NULL); if (err) { test_print("pthread_cond_init(): %d", err); goto err_free_lines; } err = pthread_mutex_init(&tracer->met_all_expected_lock, NULL); if (err) { test_print("pthread_mutex_init(): %d", err); goto err_cond_destroy; } err = setup_trace_thread(tracer); if (err) { test_print("setup_trace_thread(): %d", err); goto err_mutex_destroy; } pthread_mutex_lock(&ftracers_lock); tracer->next = ftracers; ftracers = tracer; pthread_mutex_unlock(&ftracers_lock); return tracer; err_mutex_destroy: pthread_mutex_destroy(&tracer->met_all_expected_lock); err_cond_destroy: pthread_cond_destroy(&tracer->met_all_expected); err_free_lines: free(tracer->saved_lines); err_remove: remove_ftrace_instance(tracer); err_free: free(tracer); return NULL; } static void __destroy_ftracer(struct test_ftracer *tracer) { size_t i; final_wait_for_events(tracer, TEST_TIMEOUT_SEC); stop_trace_thread(tracer); remove_ftrace_instance(tracer); if (tracer->destructor) tracer->destructor(tracer); for (i = 0; i < tracer->saved_lines_size; i++) free(tracer->saved_lines[i]); pthread_cond_destroy(&tracer->met_all_expected); pthread_mutex_destroy(&tracer->met_all_expected_lock); free(tracer); } void destroy_ftracer(struct test_ftracer *tracer) { pthread_mutex_lock(&ftracers_lock); if (tracer == ftracers) { ftracers = tracer->next; } else { struct test_ftracer *f = ftracers; while (f->next != tracer) { if (!f->next) test_error("tracers list corruption or double free %p", tracer); f = f->next; } f->next = tracer->next; } tracer->next = NULL; pthread_mutex_unlock(&ftracers_lock); __destroy_ftracer(tracer); } static void destroy_all_ftracers(void) { struct test_ftracer *f; pthread_mutex_lock(&ftracers_lock); f = ftracers; ftracers = NULL; pthread_mutex_unlock(&ftracers_lock); while (f) { struct test_ftracer *n = f->next; f->next = NULL; __destroy_ftracer(f); f = n; } } static void test_unset_tracing(void) { destroy_all_ftracers(); unmount_ftrace(); } int test_setup_tracing(void) { /* * Just a basic protection - this should be called only once from * lib/kconfig. Not thread safe, which is fine as it's early, before * threads are created. */ static int already_set; int err; if (already_set) return -1; /* Needs net-namespace cookies for filters */ if (ns_cookie1 == ns_cookie2) { test_print("net-namespace cookies: %" PRIu64 " == %" PRIu64 ", can't set up tracing", ns_cookie1, ns_cookie2); return -1; } already_set = 1; test_add_destructor(test_unset_tracing); err = mount_ftrace(); if (err) { test_print("failed to mount_ftrace(): %d", err); return err; } return setup_aolib_ftracer(); } static int get_ns_cookie(int nsfd, uint64_t *out) { int old_ns = switch_save_ns(nsfd); socklen_t size = sizeof(*out); int sk; sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sk < 0) { test_print("socket(): %m"); return -errno; } if (getsockopt(sk, SOL_SOCKET, SO_NETNS_COOKIE, out, &size)) { test_print("getsockopt(SO_NETNS_COOKIE): %m"); close(sk); return -errno; } close(sk); switch_close_ns(old_ns); return 0; } void test_init_ftrace(int nsfd1, int nsfd2) { get_ns_cookie(nsfd1, &ns_cookie1); get_ns_cookie(nsfd2, &ns_cookie2); /* Populate kernel config state */ kernel_config_has(KCONFIG_FTRACE); }