root/tools/testing/selftests/net/tcp_ao/lib/ftrace.c
// SPDX-License-Identifier: GPL-2.0
#include <inttypes.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/time.h>
#include <unistd.h>
#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);
}