root/kernel/trace/trace_pid.c
// SPDX-License-Identifier: GPL-2.0

#include "trace.h"

/**
 * trace_find_filtered_pid - check if a pid exists in a filtered_pid list
 * @filtered_pids: The list of pids to check
 * @search_pid: The PID to find in @filtered_pids
 *
 * Returns true if @search_pid is found in @filtered_pids, and false otherwise.
 */
bool
trace_find_filtered_pid(struct trace_pid_list *filtered_pids, pid_t search_pid)
{
        return trace_pid_list_is_set(filtered_pids, search_pid);
}

/**
 * trace_ignore_this_task - should a task be ignored for tracing
 * @filtered_pids: The list of pids to check
 * @filtered_no_pids: The list of pids not to be traced
 * @task: The task that should be ignored if not filtered
 *
 * Checks if @task should be traced or not from @filtered_pids.
 * Returns true if @task should *NOT* be traced.
 * Returns false if @task should be traced.
 */
bool
trace_ignore_this_task(struct trace_pid_list *filtered_pids,
                       struct trace_pid_list *filtered_no_pids,
                       struct task_struct *task)
{
        /*
         * If filtered_no_pids is not empty, and the task's pid is listed
         * in filtered_no_pids, then return true.
         * Otherwise, if filtered_pids is empty, that means we can
         * trace all tasks. If it has content, then only trace pids
         * within filtered_pids.
         */

        return (filtered_pids &&
                !trace_find_filtered_pid(filtered_pids, task->pid)) ||
                (filtered_no_pids &&
                 trace_find_filtered_pid(filtered_no_pids, task->pid));
}

/**
 * trace_filter_add_remove_task - Add or remove a task from a pid_list
 * @pid_list: The list to modify
 * @self: The current task for fork or NULL for exit
 * @task: The task to add or remove
 *
 * If adding a task, if @self is defined, the task is only added if @self
 * is also included in @pid_list. This happens on fork and tasks should
 * only be added when the parent is listed. If @self is NULL, then the
 * @task pid will be removed from the list, which would happen on exit
 * of a task.
 */
void trace_filter_add_remove_task(struct trace_pid_list *pid_list,
                                  struct task_struct *self,
                                  struct task_struct *task)
{
        if (!pid_list)
                return;

        /* For forks, we only add if the forking task is listed */
        if (self) {
                if (!trace_find_filtered_pid(pid_list, self->pid))
                        return;
        }

        /* "self" is set for forks, and NULL for exits */
        if (self)
                trace_pid_list_set(pid_list, task->pid);
        else
                trace_pid_list_clear(pid_list, task->pid);
}

/**
 * trace_pid_next - Used for seq_file to get to the next pid of a pid_list
 * @pid_list: The pid list to show
 * @v: The last pid that was shown (+1 the actual pid to let zero be displayed)
 * @pos: The position of the file
 *
 * This is used by the seq_file "next" operation to iterate the pids
 * listed in a trace_pid_list structure.
 *
 * Returns the pid+1 as we want to display pid of zero, but NULL would
 * stop the iteration.
 */
void *trace_pid_next(struct trace_pid_list *pid_list, void *v, loff_t *pos)
{
        long pid = (unsigned long)v;
        unsigned int next;

        (*pos)++;

        /* pid already is +1 of the actual previous bit */
        if (trace_pid_list_next(pid_list, pid, &next) < 0)
                return NULL;

        pid = next;

        /* Return pid + 1 to allow zero to be represented */
        return (void *)(pid + 1);
}

/**
 * trace_pid_start - Used for seq_file to start reading pid lists
 * @pid_list: The pid list to show
 * @pos: The position of the file
 *
 * This is used by seq_file "start" operation to start the iteration
 * of listing pids.
 *
 * Returns the pid+1 as we want to display pid of zero, but NULL would
 * stop the iteration.
 */
void *trace_pid_start(struct trace_pid_list *pid_list, loff_t *pos)
{
        unsigned long pid;
        unsigned int first;
        loff_t l = 0;

        if (trace_pid_list_first(pid_list, &first) < 0)
                return NULL;

        pid = first;

        /* Return pid + 1 so that zero can be the exit value */
        for (pid++; pid && l < *pos;
             pid = (unsigned long)trace_pid_next(pid_list, (void *)pid, &l))
                ;
        return (void *)pid;
}

/**
 * trace_pid_show - show the current pid in seq_file processing
 * @m: The seq_file structure to write into
 * @v: A void pointer of the pid (+1) value to display
 *
 * Can be directly used by seq_file operations to display the current
 * pid value.
 */
int trace_pid_show(struct seq_file *m, void *v)
{
        unsigned long pid = (unsigned long)v - 1;

        seq_printf(m, "%lu\n", pid);
        return 0;
}

/* 128 should be much more than enough */
#define PID_BUF_SIZE            127

int trace_pid_write(struct trace_pid_list *filtered_pids,
                    struct trace_pid_list **new_pid_list,
                    const char __user *ubuf, size_t cnt)
{
        struct trace_pid_list *pid_list;
        struct trace_parser parser;
        unsigned long val;
        int nr_pids = 0;
        ssize_t read = 0;
        ssize_t ret;
        loff_t pos;
        pid_t pid;

        if (trace_parser_get_init(&parser, PID_BUF_SIZE + 1))
                return -ENOMEM;

        /*
         * Always recreate a new array. The write is an all or nothing
         * operation. Always create a new array when adding new pids by
         * the user. If the operation fails, then the current list is
         * not modified.
         */
        pid_list = trace_pid_list_alloc();
        if (!pid_list) {
                trace_parser_put(&parser);
                return -ENOMEM;
        }

        if (filtered_pids) {
                /* copy the current bits to the new max */
                ret = trace_pid_list_first(filtered_pids, &pid);
                while (!ret) {
                        ret = trace_pid_list_set(pid_list, pid);
                        if (ret < 0)
                                goto out;

                        ret = trace_pid_list_next(filtered_pids, pid + 1, &pid);
                        nr_pids++;
                }
        }

        ret = 0;
        while (cnt > 0) {

                pos = 0;

                ret = trace_get_user(&parser, ubuf, cnt, &pos);
                if (ret < 0)
                        break;

                read += ret;
                ubuf += ret;
                cnt -= ret;

                if (!trace_parser_loaded(&parser))
                        break;

                ret = -EINVAL;
                if (kstrtoul(parser.buffer, 0, &val))
                        break;

                pid = (pid_t)val;

                if (trace_pid_list_set(pid_list, pid) < 0) {
                        ret = -1;
                        break;
                }
                nr_pids++;

                trace_parser_clear(&parser);
                ret = 0;
        }
 out:
        trace_parser_put(&parser);

        if (ret < 0) {
                trace_pid_list_free(pid_list);
                return ret;
        }

        if (!nr_pids) {
                /* Cleared the list of pids */
                trace_pid_list_free(pid_list);
                pid_list = NULL;
        }

        *new_pid_list = pid_list;

        return read;
}