// SPDX-License-Identifier: GPL-3.0-or-later

#include "ebpf.h"
#include "ebpf_socket.h"
#include "ebpf_apps.h"

// ----------------------------------------------------------------------------
// internal flags
// handled in code (automatically set)

static int proc_pid_cmdline_is_needed = 0; // 1 when we need to read /proc/cmdline

/*****************************************************************
 *
 *  FUNCTIONS USED TO READ HASH TABLES
 *
 *****************************************************************/

/**
 * Read statistic hash table.
 *
 * @param ep                    the output structure.
 * @param fd                    the file descriptor mapped from kernel ring.
 * @param pid                   the index used to select the data.
 * @param bpf_map_lookup_elem   a pointer for the function used to read data.
 *
 * @return It returns 0 when the data was copied and -1 otherwise
 */
int ebpf_read_hash_table(void *ep, int fd, uint32_t pid)
{
    if (!ep)
        return -1;

    if (!bpf_map_lookup_elem(fd, &pid, ep))
        return 0;

    return -1;
}

/**
 * Read socket statistic
 *
 * Read information from kernel ring to user ring.
 *
 * @param ep    the table with all process stats values.
 * @param fd    the file descriptor mapped from kernel
 * @param ef    a pointer for the functions mapped from dynamic library
 * @param pids  the list of pids associated to a target.
 *
 * @return
 */
size_t read_bandwidth_statistic_using_pid_on_target(ebpf_bandwidth_t **ep, int fd, struct pid_on_target *pids)
{
    size_t count = 0;
    while (pids) {
        uint32_t current_pid = pids->pid;
        if (!ebpf_read_hash_table(ep[current_pid], fd, current_pid))
            count++;

        pids = pids->next;
    }

    return count;
}

/**
 * Read bandwidth statistic using hash table
 *
 * @param out                   the output tensor that will receive the information.
 * @param fd                    the file descriptor that has the data
 * @param bpf_map_lookup_elem   a pointer for the function to read the data
 * @param bpf_map_get_next_key  a pointer fo the function to read the index.
 */
size_t read_bandwidth_statistic_using_hash_table(ebpf_bandwidth_t **out, int fd)
{
    size_t count = 0;
    uint32_t key = 0;
    uint32_t next_key = 0;

    while (bpf_map_get_next_key(fd, &key, &next_key) == 0) {
        ebpf_bandwidth_t *eps = out[next_key];
        if (!eps) {
            eps = callocz(1, sizeof(ebpf_process_stat_t));
            out[next_key] = eps;
        }
        ebpf_read_hash_table(eps, fd, next_key);
    }

    return count;
}

/*****************************************************************
 *
 *  FUNCTIONS CALLED FROM COLLECTORS
 *
 *****************************************************************/

/**
 * Am I running as Root
 *
 * Verify the user that is running the collector.
 *
 * @return It returns 1 for root and 0 otherwise.
 */
int am_i_running_as_root()
{
    uid_t uid = getuid(), euid = geteuid();

    if (uid == 0 || euid == 0) {
        return 1;
    }

    return 0;
}

/**
 * Reset the target values
 *
 * @param root the pointer to the chain that will be reset.
 *
 * @return it returns the number of structures that was reset.
 */
size_t zero_all_targets(struct target *root)
{
    struct target *w;
    size_t count = 0;

    for (w = root; w; w = w->next) {
        count++;

        if (unlikely(w->root_pid)) {
            struct pid_on_target *pid_on_target = w->root_pid;

            while (pid_on_target) {
                struct pid_on_target *pid_on_target_to_free = pid_on_target;
                pid_on_target = pid_on_target->next;
                free(pid_on_target_to_free);
            }

            w->root_pid = NULL;
        }
    }

    return count;
}

/**
 * Clean the allocated structures
 *
 * @param agrt the pointer to be cleaned.
 */
void clean_apps_groups_target(struct target *agrt)
{
    struct target *current_target;
    while (agrt) {
        current_target = agrt;
        agrt = current_target->target;

        freez(current_target);
    }
}

/**
 * Find or create a new target
 * there are targets that are just aggregated to other target (the second argument)
 *
 * @param id
 * @param target
 * @param name
 *
 * @return It returns the target on success and NULL otherwise
 */
struct target *get_apps_groups_target(struct target **agrt, const char *id, struct target *target, const char *name)
{
    int tdebug = 0, thidden = target ? target->hidden : 0, ends_with = 0;
    const char *nid = id;

    // extract the options
    while (nid[0] == '-' || nid[0] == '+' || nid[0] == '*') {
        if (nid[0] == '-')
            thidden = 1;
        if (nid[0] == '+')
            tdebug = 1;
        if (nid[0] == '*')
            ends_with = 1;
        nid++;
    }
    uint32_t hash = simple_hash(id);

    // find if it already exists
    struct target *w, *last = *agrt;
    for (w = *agrt; w; w = w->next) {
        if (w->idhash == hash && strncmp(nid, w->id, MAX_NAME) == 0)
            return w;

        last = w;
    }

    // find an existing target
    if (unlikely(!target)) {
        while (*name == '-') {
            if (*name == '-')
                thidden = 1;
            name++;
        }

        for (target = *agrt; target != NULL; target = target->next) {
            if (!target->target && strcmp(name, target->name) == 0)
                break;
        }
    }

    if (target && target->target)
        fatal(
            "Internal Error: request to link process '%s' to target '%s' which is linked to target '%s'", id,
            target->id, target->target->id);

    w = callocz(1, sizeof(struct target));
    strncpyz(w->id, nid, MAX_NAME);
    w->idhash = simple_hash(w->id);

    if (unlikely(!target))
        // copy the name
        strncpyz(w->name, name, MAX_NAME);
    else
        // copy the id
        strncpyz(w->name, nid, MAX_NAME);

    strncpyz(w->compare, nid, MAX_COMPARE_NAME);
    size_t len = strlen(w->compare);
    if (w->compare[len - 1] == '*') {
        w->compare[len - 1] = '\0';
        w->starts_with = 1;
    }
    w->ends_with = ends_with;

    if (w->starts_with && w->ends_with)
        proc_pid_cmdline_is_needed = 1;

    w->comparehash = simple_hash(w->compare);
    w->comparelen = strlen(w->compare);

    w->hidden = thidden;
#ifdef NETDATA_INTERNAL_CHECKS
    w->debug_enabled = tdebug;
#else
    if (tdebug)
        fprintf(stderr, "apps.plugin has been compiled without debugging\n");
#endif
    w->target = target;

    // append it, to maintain the order in apps_groups.conf
    if (last)
        last->next = w;
    else
        *agrt = w;

    return w;
}

/**
 * Read the apps_groups.conf file
 *
 * @param agrt a pointer to apps_group_root_target
 * @param path the directory to search apps_%s.conf
 * @param file the word to complement the file name.
 *
 * @return It returns 0 on success and -1 otherwise
 */
int ebpf_read_apps_groups_conf(struct target **agdt, struct target **agrt, const char *path, const char *file)
{
    char filename[FILENAME_MAX + 1];

    snprintfz(filename, FILENAME_MAX, "%s/apps_%s.conf", path, file);

    // ----------------------------------------

    procfile *ff = procfile_open_no_log(filename, " :\t", PROCFILE_FLAG_DEFAULT);
    if (!ff)
        return -1;

    procfile_set_quotes(ff, "'\"");

    ff = procfile_readall(ff);
    if (!ff)
        return -1;

    size_t line, lines = procfile_lines(ff);

    for (line = 0; line < lines; line++) {
        size_t word, words = procfile_linewords(ff, line);
        if (!words)
            continue;

        char *name = procfile_lineword(ff, line, 0);
        if (!name || !*name)
            continue;

        // find a possibly existing target
        struct target *w = NULL;

        // loop through all words, skipping the first one (the name)
        for (word = 0; word < words; word++) {
            char *s = procfile_lineword(ff, line, word);
            if (!s || !*s)
                continue;
            if (*s == '#')
                break;

            // is this the first word? skip it
            if (s == name)
                continue;

            // add this target
            struct target *n = get_apps_groups_target(agrt, s, w, name);
            if (!n) {
                error("Cannot create target '%s' (line %zu, word %zu)", s, line, word);
                continue;
            }

            // just some optimization
            // to avoid searching for a target for each process
            if (!w)
                w = n->target ? n->target : n;
        }
    }

    procfile_close(ff);

    *agdt = get_apps_groups_target(agrt, "p+!o@w#e$i^r&7*5(-i)l-o_", NULL, "other"); // match nothing
    if (!*agdt)
        fatal("Cannot create default target");

    struct target *ptr = *agdt;
    if (ptr->target)
        *agdt = ptr->target;

    return 0;
}

// the minimum PID of the system
// this is also the pid of the init process
#define INIT_PID 1

// ----------------------------------------------------------------------------
// string lengths

#define MAX_COMPARE_NAME 100
#define MAX_NAME 100
#define MAX_CMDLINE 16384

struct pid_stat **all_pids = NULL;    // to avoid allocations, we pre-allocate the
                                      // the entire pid space.
struct pid_stat *root_of_pids = NULL; // global list of all processes running

size_t all_pids_count = 0; // the number of processes running

struct target
    *apps_groups_default_target = NULL, // the default target
    *apps_groups_root_target = NULL,    // apps_groups.conf defined
    *users_root_target = NULL,          // users
    *groups_root_target = NULL;         // user groups

size_t apps_groups_targets_count = 0; // # of apps_groups.conf targets

// ----------------------------------------------------------------------------
// internal counters

static size_t
    // global_iterations_counter = 1,
    calls_counter = 0,
    // file_counter = 0,
    // filenames_allocated_counter = 0,
    // inodes_changed_counter = 0,
    // links_changed_counter = 0,
    targets_assignment_counter = 0;

// ----------------------------------------------------------------------------
// debugging

// log each problem once per process
// log flood protection flags (log_thrown)
#define PID_LOG_IO 0x00000001
#define PID_LOG_STATUS 0x00000002
#define PID_LOG_CMDLINE 0x00000004
#define PID_LOG_FDS 0x00000008
#define PID_LOG_STAT 0x00000010

int debug_enabled = 0;

#ifdef NETDATA_INTERNAL_CHECKS

#define debug_log(fmt, args...)                                                                                        \
    do {                                                                                                               \
        if (unlikely(debug_enabled))                                                                                   \
            debug_log_int(fmt, ##args);                                                                                \
    } while (0)

#else

static inline void debug_log_dummy(void)
{
}
#define debug_log(fmt, args...) debug_log_dummy()

#endif

/**
 * Managed log
 *
 * Store log information if it is necessary.
 *
 * @param p         the pid stat structure
 * @param log       the log id
 * @param status    the return from a function.
 *
 * @return It returns the status value.
 */
static inline int managed_log(struct pid_stat *p, uint32_t log, int status)
{
    if (unlikely(!status)) {
        // error("command failed log %u, errno %d", log, errno);

        if (unlikely(debug_enabled || errno != ENOENT)) {
            if (unlikely(debug_enabled || !(p->log_thrown & log))) {
                p->log_thrown |= log;
                switch (log) {
                    case PID_LOG_IO:
                        error(
                            "Cannot process %s/proc/%d/io (command '%s')", netdata_configured_host_prefix, p->pid,
                            p->comm);
                        break;

                    case PID_LOG_STATUS:
                        error(
                            "Cannot process %s/proc/%d/status (command '%s')", netdata_configured_host_prefix, p->pid,
                            p->comm);
                        break;

                    case PID_LOG_CMDLINE:
                        error(
                            "Cannot process %s/proc/%d/cmdline (command '%s')", netdata_configured_host_prefix, p->pid,
                            p->comm);
                        break;

                    case PID_LOG_FDS:
                        error(
                            "Cannot process entries in %s/proc/%d/fd (command '%s')", netdata_configured_host_prefix,
                            p->pid, p->comm);
                        break;

                    case PID_LOG_STAT:
                        break;

                    default:
                        error("unhandled error for pid %d, command '%s'", p->pid, p->comm);
                        break;
                }
            }
        }
        errno = 0;
    } else if (unlikely(p->log_thrown & log)) {
        // error("unsetting log %u on pid %d", log, p->pid);
        p->log_thrown &= ~log;
    }

    return status;
}

/**
 * Get PID entry
 *
 * Get or allocate the PID entry for the specified pid.
 *
 * @param pid the pid to search the data.
 *
 * @return It returns the pid entry structure
 */
static inline struct pid_stat *get_pid_entry(pid_t pid)
{
    if (unlikely(all_pids[pid]))
        return all_pids[pid];

    struct pid_stat *p = callocz(1, sizeof(struct pid_stat));

    if (likely(root_of_pids))
        root_of_pids->prev = p;

    p->next = root_of_pids;
    root_of_pids = p;

    p->pid = pid;

    all_pids[pid] = p;
    all_pids_count++;

    return p;
}

/**
 * Assign the PID to a target.
 *
 * @param p the pid_stat structure to assign for a target.
 */
static inline void assign_target_to_pid(struct pid_stat *p)
{
    targets_assignment_counter++;

    uint32_t hash = simple_hash(p->comm);
    size_t pclen = strlen(p->comm);

    struct target *w;
    for (w = apps_groups_root_target; w; w = w->next) {
        // if(debug_enabled || (p->target && p->target->debug_enabled)) debug_log_int("\t\tcomparing '%s' with '%s'", w->compare, p->comm);

        // find it - 4 cases:
        // 1. the target is not a pattern
        // 2. the target has the prefix
        // 3. the target has the suffix
        // 4. the target is something inside cmdline

        if (unlikely(
                ((!w->starts_with && !w->ends_with && w->comparehash == hash && !strcmp(w->compare, p->comm)) ||
                 (w->starts_with && !w->ends_with && !strncmp(w->compare, p->comm, w->comparelen)) ||
                 (!w->starts_with && w->ends_with && pclen >= w->comparelen && !strcmp(w->compare, &p->comm[pclen - w->comparelen])) ||
                 (proc_pid_cmdline_is_needed && w->starts_with && w->ends_with && p->cmdline && strstr(p->cmdline, w->compare))))) {
            if (w->target)
                p->target = w->target;
            else
                p->target = w;

            if (debug_enabled || (p->target && p->target->debug_enabled))
                debug_log_int("%s linked to target %s", p->comm, p->target->name);

            break;
        }
    }
}

// ----------------------------------------------------------------------------
// update pids from proc

/**
 * Read cmd line from /proc/PID/cmdline
 *
 * @param p  the pid_stat_structure.
 *
 * @return It returns 1 on success and 0 otherwise.
 */
static inline int read_proc_pid_cmdline(struct pid_stat *p)
{
    static char cmdline[MAX_CMDLINE + 1];

    if (unlikely(!p->cmdline_filename)) {
        char filename[FILENAME_MAX + 1];
        snprintfz(filename, FILENAME_MAX, "%s/proc/%d/cmdline", netdata_configured_host_prefix, p->pid);
        p->cmdline_filename = strdupz(filename);
    }

    int fd = open(p->cmdline_filename, procfile_open_flags, 0666);
    if (unlikely(fd == -1))
        goto cleanup;

    ssize_t i, bytes = read(fd, cmdline, MAX_CMDLINE);
    close(fd);

    if (unlikely(bytes < 0))
        goto cleanup;

    cmdline[bytes] = '\0';
    for (i = 0; i < bytes; i++) {
        if (unlikely(!cmdline[i]))
            cmdline[i] = ' ';
    }

    if (p->cmdline)
        freez(p->cmdline);
    p->cmdline = strdupz(cmdline);

    debug_log("Read file '%s' contents: %s", p->cmdline_filename, p->cmdline);

    return 1;

cleanup:
    // copy the command to the command line
    if (p->cmdline)
        freez(p->cmdline);
    p->cmdline = strdupz(p->comm);
    return 0;
}

/**
 * Read information from /proc/PID/stat and /proc/PID/cmdline
 * Assign target to pid
 *
 * @param p the pid stat structure to store the data.
 * @param ptr an useless argument.
 */
static inline int read_proc_pid_stat(struct pid_stat *p, void *ptr)
{
    UNUSED(ptr);

    static procfile *ff = NULL;

    if (unlikely(!p->stat_filename)) {
        char filename[FILENAME_MAX + 1];
        snprintfz(filename, FILENAME_MAX, "%s/proc/%d/stat", netdata_configured_host_prefix, p->pid);
        p->stat_filename = strdupz(filename);
    }

    int set_quotes = (!ff) ? 1 : 0;

    struct stat statbuf;
    if (stat(p->stat_filename, &statbuf))
        return 0;

    ff = procfile_reopen(ff, p->stat_filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
    if (unlikely(!ff))
        return 0;

    if (unlikely(set_quotes))
        procfile_set_open_close(ff, "(", ")");

    ff = procfile_readall(ff);
    if (unlikely(!ff))
        return 0;

    p->last_stat_collected_usec = p->stat_collected_usec;
    p->stat_collected_usec = now_monotonic_usec();
    calls_counter++;

    char *comm = procfile_lineword(ff, 0, 1);
    p->ppid = (int32_t)str2pid_t(procfile_lineword(ff, 0, 3));

    if (strcmp(p->comm, comm) != 0) {
        if (unlikely(debug_enabled)) {
            if (p->comm[0])
                debug_log("\tpid %d (%s) changed name to '%s'", p->pid, p->comm, comm);
            else
                debug_log("\tJust added %d (%s)", p->pid, comm);
        }

        strncpyz(p->comm, comm, MAX_COMPARE_NAME);

        // /proc/<pid>/cmdline
        if (likely(proc_pid_cmdline_is_needed))
            managed_log(p, PID_LOG_CMDLINE, read_proc_pid_cmdline(p));

        assign_target_to_pid(p);
    }

    if (unlikely(debug_enabled || (p->target && p->target->debug_enabled)))
        debug_log_int(
            "READ PROC/PID/STAT: %s/proc/%d/stat, process: '%s' on target '%s' (dt=%llu)",
            netdata_configured_host_prefix, p->pid, p->comm, (p->target) ? p->target->name : "UNSET",
            p->stat_collected_usec - p->last_stat_collected_usec);

    return 1;
}

/**
 * Collect data for PID
 *
 * @param pid the current pid that we are working
 * @param ptr a NULL value
 *
 * @return It returns 1 on success and 0 otherwise
 */
static inline int collect_data_for_pid(pid_t pid, void *ptr)
{
    if (unlikely(pid < 0 || pid > pid_max)) {
        error("Invalid pid %d read (expected %d to %d). Ignoring process.", pid, 0, pid_max);
        return 0;
    }

    struct pid_stat *p = get_pid_entry(pid);
    if (unlikely(!p || p->read))
        return 0;
    p->read = 1;

    if (unlikely(!managed_log(p, PID_LOG_STAT, read_proc_pid_stat(p, ptr))))
        // there is no reason to proceed if we cannot get its status
        return 0;

    // check its parent pid
    if (unlikely(p->ppid < 0 || p->ppid > pid_max)) {
        error("Pid %d (command '%s') states invalid parent pid %d. Using 0.", pid, p->comm, p->ppid);
        p->ppid = 0;
    }

    // mark it as updated
    p->updated = 1;
    p->keep = 0;
    p->keeploops = 0;

    return 1;
}

/**
 * Fill link list of parents with children PIDs
 */
static inline void link_all_processes_to_their_parents(void)
{
    struct pid_stat *p, *pp;

    // link all children to their parents
    // and update children count on parents
    for (p = root_of_pids; p; p = p->next) {
        // for each process found

        p->sortlist = 0;
        p->parent = NULL;

        if (unlikely(!p->ppid)) {
            p->parent = NULL;
            continue;
        }

        pp = all_pids[p->ppid];
        if (likely(pp)) {
            p->parent = pp;
            pp->children_count++;

            if (unlikely(debug_enabled || (p->target && p->target->debug_enabled)))
                debug_log_int(
                    "child %d (%s, %s) on target '%s' has parent %d (%s, %s).", p->pid, p->comm,
                    p->updated ? "running" : "exited", (p->target) ? p->target->name : "UNSET", pp->pid, pp->comm,
                    pp->updated ? "running" : "exited");
        } else {
            p->parent = NULL;
            debug_log("pid %d %s states parent %d, but the later does not exist.", p->pid, p->comm, p->ppid);
        }
    }
}

/**
 * Aggregate PIDs to targets.
 */
static void apply_apps_groups_targets_inheritance(void)
{
    struct pid_stat *p = NULL;

    // children that do not have a target
    // inherit their target from their parent
    int found = 1, loops = 0;
    while (found) {
        if (unlikely(debug_enabled))
            loops++;
        found = 0;
        for (p = root_of_pids; p; p = p->next) {
            // if this process does not have a target
            // and it has a parent
            // and its parent has a target
            // then, set the parent's target to this process
            if (unlikely(!p->target && p->parent && p->parent->target)) {
                p->target = p->parent->target;
                found++;

                if (debug_enabled || (p->target && p->target->debug_enabled))
                    debug_log_int(
                        "TARGET INHERITANCE: %s is inherited by %d (%s) from its parent %d (%s).", p->target->name,
                        p->pid, p->comm, p->parent->pid, p->parent->comm);
            }
        }
    }

    // find all the procs with 0 childs and merge them to their parents
    // repeat, until nothing more can be done.
    int sortlist = 1;
    found = 1;
    while (found) {
        if (unlikely(debug_enabled))
            loops++;
        found = 0;

        for (p = root_of_pids; p; p = p->next) {
            if (unlikely(!p->sortlist && !p->children_count))
                p->sortlist = sortlist++;

            if (unlikely(
                    !p->children_count           // if this process does not have any children
                    && !p->merged                // and is not already merged
                    && p->parent                 // and has a parent
                    && p->parent->children_count // and its parent has children
                                                 // and the target of this process and its parent is the same,
                                                 // or the parent does not have a target
                    && (p->target == p->parent->target || !p->parent->target) &&
                    p->ppid != INIT_PID // and its parent is not init
                    )) {
                // mark it as merged
                p->parent->children_count--;
                p->merged = 1;

                // the parent inherits the child's target, if it does not have a target itself
                if (unlikely(p->target && !p->parent->target)) {
                    p->parent->target = p->target;

                    if (debug_enabled || (p->target && p->target->debug_enabled))
                        debug_log_int(
                            "TARGET INHERITANCE: %s is inherited by %d (%s) from its child %d (%s).", p->target->name,
                            p->parent->pid, p->parent->comm, p->pid, p->comm);
                }

                found++;
            }
        }

        debug_log("TARGET INHERITANCE: merged %d processes", found);
    }

    // init goes always to default target
    if (all_pids[INIT_PID])
        all_pids[INIT_PID]->target = apps_groups_default_target;

    // pid 0 goes always to default target
    if (all_pids[0])
        all_pids[0]->target = apps_groups_default_target;

    // give a default target on all top level processes
    if (unlikely(debug_enabled))
        loops++;
    for (p = root_of_pids; p; p = p->next) {
        // if the process is not merged itself
        // then is is a top level process
        if (unlikely(!p->merged && !p->target))
            p->target = apps_groups_default_target;

        // make sure all processes have a sortlist
        if (unlikely(!p->sortlist))
            p->sortlist = sortlist++;
    }

    if (all_pids[1])
        all_pids[1]->sortlist = sortlist++;

    // give a target to all merged child processes
    found = 1;
    while (found) {
        if (unlikely(debug_enabled))
            loops++;
        found = 0;
        for (p = root_of_pids; p; p = p->next) {
            if (unlikely(!p->target && p->merged && p->parent && p->parent->target)) {
                p->target = p->parent->target;
                found++;

                if (debug_enabled || (p->target && p->target->debug_enabled))
                    debug_log_int(
                        "TARGET INHERITANCE: %s is inherited by %d (%s) from its parent %d (%s) at phase 2.",
                        p->target->name, p->pid, p->comm, p->parent->pid, p->parent->comm);
            }
        }
    }

    debug_log("apply_apps_groups_targets_inheritance() made %d loops on the process tree", loops);
}

/**
 * Update target timestamp.
 *
 * @param root the targets that will be updated.
 */
static inline void post_aggregate_targets(struct target *root)
{
    struct target *w;
    for (w = root; w; w = w->next) {
        if (w->collected_starttime) {
            if (!w->starttime || w->collected_starttime < w->starttime) {
                w->starttime = w->collected_starttime;
            }
        } else {
            w->starttime = 0;
        }
    }
}

/**
 * Remove PID from the link list.
 *
 * @param pid the PID that will be removed.
 */
static inline void del_pid_entry(pid_t pid)
{
    struct pid_stat *p = all_pids[pid];

    if (unlikely(!p)) {
        error("attempted to free pid %d that is not allocated.", pid);
        return;
    }

    debug_log("process %d %s exited, deleting it.", pid, p->comm);

    if (root_of_pids == p)
        root_of_pids = p->next;

    if (p->next)
        p->next->prev = p->prev;
    if (p->prev)
        p->prev->next = p->next;

    freez(p->stat_filename);
    freez(p->status_filename);
    freez(p->io_filename);
    freez(p->cmdline_filename);
    freez(p->cmdline);
    freez(p);

    all_pids[pid] = NULL;
    all_pids_count--;
}

/**
 * Get command string associated with a PID.
 * This can only safely be used when holding the `collect_data_mutex` lock.
 *
 * @param pid the pid to search the data.
 * @param n the maximum amount of bytes to copy into dest.
 *          if this is greater than the size of the command, it is clipped.
 * @param dest the target memory buffer to write the command into.
 * @return -1 if the PID hasn't been scraped yet, 0 otherwise.
 */
int get_pid_comm(pid_t pid, size_t n, char *dest)
{
    struct pid_stat *stat;

    stat = all_pids[pid];
    if (unlikely(stat == NULL)) {
        return -1;
    }

    if (unlikely(n > sizeof(stat->comm))) {
        n = sizeof(stat->comm);
    }

    strncpyz(dest, stat->comm, n);
    return 0;
}

/**
 * Cleanup variable from other threads
 *
 * @param pid current pid.
 */
void cleanup_variables_from_other_threads(uint32_t pid)
{
    // Clean socket structures
    if (socket_bandwidth_curr) {
        freez(socket_bandwidth_curr[pid]);
        socket_bandwidth_curr[pid] = NULL;
    }

    // Clean cachestat structure
    if (cachestat_pid) {
        freez(cachestat_pid[pid]);
        cachestat_pid[pid] = NULL;
    }

    // Clean directory cache structure
    if (dcstat_pid) {
        freez(dcstat_pid[pid]);
        dcstat_pid[pid] = NULL;
    }

    // Clean swap structure
    if (swap_pid) {
        freez(swap_pid[pid]);
        swap_pid[pid] = NULL;
    }

    // Clean vfs structure
    if (vfs_pid) {
        freez(vfs_pid[pid]);
        vfs_pid[pid] = NULL;
    }

    // Clean fd structure
    if (fd_pid) {
        freez(fd_pid[pid]);
        fd_pid[pid] = NULL;
    }

    // Clean shm structure
    if (shm_pid) {
        freez(shm_pid[pid]);
        shm_pid[pid] = NULL;
    }
}

/**
 * Remove PIDs when they are not running more.
 */
void cleanup_exited_pids()
{
    struct pid_stat *p = NULL;

    for (p = root_of_pids; p;) {
        if (!p->updated && (!p->keep || p->keeploops > 0)) {
            if (unlikely(debug_enabled && (p->keep || p->keeploops)))
                debug_log(" > CLEANUP cannot keep exited process %d (%s) anymore - removing it.", p->pid, p->comm);

            pid_t r = p->pid;
            p = p->next;

            // Clean process structure
            freez(global_process_stats[r]);
            global_process_stats[r] = NULL;

            freez(current_apps_data[r]);
            current_apps_data[r] = NULL;

            cleanup_variables_from_other_threads(r);

            del_pid_entry(r);
        } else {
            if (unlikely(p->keep))
                p->keeploops++;
            p->keep = 0;
            p = p->next;
        }
    }
}

/**
 * Read proc filesystem for the first time.
 *
 * @return It returns 0 on success and -1 otherwise.
 */
static inline void read_proc_filesystem()
{
    char dirname[FILENAME_MAX + 1];

    snprintfz(dirname, FILENAME_MAX, "%s/proc", netdata_configured_host_prefix);
    DIR *dir = opendir(dirname);
    if (!dir)
        return;

    struct dirent *de = NULL;

    while ((de = readdir(dir))) {
        char *endptr = de->d_name;

        if (unlikely(de->d_type != DT_DIR || de->d_name[0] < '0' || de->d_name[0] > '9'))
            continue;

        pid_t pid = (pid_t)strtoul(de->d_name, &endptr, 10);

        // make sure we read a valid number
        if (unlikely(endptr == de->d_name || *endptr != '\0'))
            continue;

        collect_data_for_pid(pid, NULL);
    }
    closedir(dir);
}

/**
 * Aggregated PID on target
 *
 * @param w the target output
 * @param p the pid with information to update
 * @param o never used
 */
static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p, struct target *o)
{
    UNUSED(o);

    if (unlikely(!p->updated)) {
        // the process is not running
        return;
    }

    if (unlikely(!w)) {
        error("pid %d %s was left without a target!", p->pid, p->comm);
        return;
    }

    w->processes++;
    struct pid_on_target *pid_on_target = mallocz(sizeof(struct pid_on_target));
    pid_on_target->pid = p->pid;
    pid_on_target->next = w->root_pid;
    w->root_pid = pid_on_target;
}

/**
 * Collect data for all process
 *
 * Read data from hash table and store it in appropriate vectors.
 * It also creates the link between targets and PIDs.
 *
 * @param tbl_pid_stats_fd      The mapped file descriptor for the hash table.
 */
void collect_data_for_all_processes(int tbl_pid_stats_fd)
{
    if (unlikely(!all_pids))
        return;

    struct pid_stat *pids = root_of_pids; // global list of all processes running
    while (pids) {
        if (pids->updated_twice) {
            pids->read = 0; // mark it as not read, so that collect_data_for_pid() will read it
            pids->updated = 0;
            pids->merged = 0;
            pids->children_count = 0;
            pids->parent = NULL;
        } else {
            if (pids->updated)
                pids->updated_twice = 1;
        }

        pids = pids->next;
    }

    read_proc_filesystem();

    uint32_t key;
    pids = root_of_pids; // global list of all processes running
    // while (bpf_map_get_next_key(tbl_pid_stats_fd, &key, &next_key) == 0) {
    while (pids) {
        key = pids->pid;
        ebpf_process_stat_t *w = global_process_stats[key];
        if (!w) {
            w = mallocz(sizeof(ebpf_process_stat_t));
            global_process_stats[key] = w;
        }

        if (bpf_map_lookup_elem(tbl_pid_stats_fd, &key, w)) {
            // Clean Process structures
            freez(w);
            global_process_stats[key] = NULL;

            freez(current_apps_data[key]);
            current_apps_data[key] = NULL;

            cleanup_variables_from_other_threads(key);

            pids = pids->next;
            continue;
        }

        pids = pids->next;
    }

    link_all_processes_to_their_parents();

    apply_apps_groups_targets_inheritance();

    apps_groups_targets_count = zero_all_targets(apps_groups_root_target);

    // this has to be done, before the cleanup
    // // concentrate everything on the targets
    for (pids = root_of_pids; pids; pids = pids->next)
        aggregate_pid_on_target(pids->target, pids, NULL);

    post_aggregate_targets(apps_groups_root_target);
}
