#if defined(__MACH__) && defined(__APPLE__)
#define __MacOS__
#endif

#include "ui/gettext.h"
#define _(STR) gettext(STR)

#if defined(__MacOS__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <sys/proc.h>
#include <libgen.h>
#endif

#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
#include <libgen.h>
#include <unistd.h>
#include <fcntl.h>
#include <kvm.h>
#include <limits.h>
#include <sys/proc.h>
#include <sys/param.h>
#include <sys/resource.h>
#endif

#if defined(__FreeBSD__)
#define _WANT_FILE
#include <sys/file.h>
#include <sys/filedesc.h>
#endif

#if defined(__MacOS__)
#include <libproc.h>
#include <sys/proc_info.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <limits.h>

#include "process.h"
#include <Eina.h>
#include <Ecore.h>
#include <Ecore_File.h>

#if defined(__linux__) && !defined(PF_KTHREAD)
#define PF_KTHREAD 0x00200000
#endif

#if defined(__linux__)
#include <stddef.h>
#include <dirent.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <linux/inet_diag.h>
#include <linux/sock_diag.h>
#include <linux/tcp.h>
#include <linux/rtnetlink.h>
#endif

#include "macros.h"

static Eina_Bool _show_kthreads = 1;

void
proc_info_kthreads_show_set(Eina_Bool enabled) {
    _show_kthreads = enabled;
}

Eina_Bool
proc_info_kthreads_show_get(void) {
    return _show_kthreads;
}

static const char *_states[128];

static void
_states_init(void) {
#if defined(__linux__)
    _states['D'] = _("dsleep");
    _states['I'] = _("idle");
    _states['R'] = _("run");
    _states['S'] = _("sleep");
    _states['T'] = _("stop");
    _states['X'] = _("dead");
    _states['Z'] = _("zombie");
#else
    _states[SIDL] = _("idle");
    _states[SRUN] = _("run");
    _states[SSLEEP] = _("sleep");
    _states[SSTOP] = _("stop");
#if !defined(__MacOS__)
#if !defined(__OpenBSD__)
    _states[SWAIT] = _("wait");
    _states[SLOCK] = _("lock");
    _states[SZOMB] = _("zombie");
#endif
#if defined(__OpenBSD__)
    _states[SDEAD] = _("zombie");
    _states[SONPROC] = _("run");
#endif
#endif
#endif
}

static const char *
_process_state_name(char state) {
    static int init = 0;
    if (!init) {
        _states_init();
        init = 1;
    }
    return _states[toupper(state)];
}

#if defined(__linux__)

static unsigned long
_parse_line(char *line) {
    char *p, *tok;

    p = strchr(line, ':') + 1;
    while (isspace(*p)) p++;
    tok = strtok(p, " ");

    return atol(tok);
}

static void
_mem_size(Proc_Info *proc) {
    FILE *f;
    char buf[4096];
    unsigned int dummy, size, shared, resident, data, text;
    static int pagesize = 0;

    if (!pagesize) pagesize = getpagesize();

    snprintf(buf, sizeof(buf), "/proc/%d/statm", proc->pid);
    f = fopen(buf, "r");
    if (!f) return;

    if (fgets(buf, sizeof(buf), f)) {
        if (sscanf(buf, "%u %u %u %u %u %u %u", &size, &resident, &shared, &text, &dummy, &data, &dummy) == 7) {
            proc->mem_rss = MEMSIZE(resident) * MEMSIZE(pagesize);
            proc->mem_shared = MEMSIZE(shared) * MEMSIZE(pagesize);
            proc->mem_size = proc->mem_rss - proc->mem_shared;
            proc->mem_virt = MEMSIZE(size) * MEMSIZE(pagesize);
        }
    }

    fclose(f);
}

static void
_cmd_args(Proc_Info *p, char *name, size_t len) {
    char path[PATH_MAX];
    char line[4096];
    pid_t pid = p->pid;

    snprintf(path, sizeof(path), "/proc/%i/exe", pid);
    char *link = ecore_file_readlink(path);
    if (link) {
        snprintf(name, len, "%s", ecore_file_file_get(link));
        free(link);
    }

    snprintf(path, sizeof(path), "/proc/%i/cmdline", pid);
    FILE *f = fopen(path, "r");
    if (f) {
        if (fgets(line, sizeof(line), f)) {
            int sz = ftell(f);
            Eina_Strbuf *buf = eina_strbuf_new();
            char *line2 = strdup(line);
            if (!buf) {
                free(line2);
                fclose(f);
                return;
            }

            if (line2) { // copy line up to first blank into line2 then 0 terminate
                char *p, *p2, *file;

                for (p = line, p2 = line2; *p; p++, p2++) {
                    if (isblank(*p)) {
                        *p2 = '\0';
                        break;
                    }
                    p2[0] = p[0];
                    p2[1] = '\0';
                }
                // use file portion of this path as the name
                if ((line2[0] >= 'A') && (line2[0] <= 'Z') && (line2[1] == ':')
                    && (line2[2] == '\\')) { // special case what looks like as wine/proton windows
                    // exe cmdline. fine last backslash similar to below
                    file = strrchr(line2, '\\');
                    if (file) file++;
                    else file = line2;
                } else { // get last / and use name of file after that if / exists
                    file = strrchr(line2, '/');
                    if (file) file++;
                    else file = line2;
                }
                snprintf(name, len, "%s", file);
                free(line2);
            } else name[0] = '\0';

            const char *cp = line;
            for (int i = 0; i < sz; i++) {
                if (line[i] == '\0') {
                    if (*cp) eina_strbuf_append(buf, cp);
                    if ((i + 1) < sz) {
                        i++;
                        cp = &line[i];
                        if (*cp) eina_strbuf_append(buf, " ");
                    }
                }
            }
            p->arguments = eina_strbuf_release(buf);
        }
        fclose(f);
    }

    char *end = strchr(name, ' ');
    if (end) *end = '\0';
    end = strchr(name, ':');
    if (end) *end = '\0';

    p->command = strdup(name);
    if (!p->command) p->command = strdup("");
}

static int
_uid(pid_t pid) {
    FILE *f;
    char buf[4096];
    int uid = 0;

    snprintf(buf, sizeof(buf), "/proc/%d/status", pid);
    f = fopen(buf, "r");
    if (!f) return -1;

    while ((fgets(buf, sizeof(buf), f)) != NULL) {
        if (!strncmp(buf, "Uid:", 4)) {
            uid = _parse_line(buf);
            break;
        }
    }

    fclose(f);

    return uid;
}

static int64_t
_boot_time(void) {
    FILE *f;
    int64_t boot_time = 0;
    char buf[4096];
    double uptime = 0.0;

    f = fopen("/proc/uptime", "r");
    if (!f) return 0;

    if (fgets(buf, sizeof(buf), f)) sscanf(buf, "%lf", &uptime);

    fclose(f);

    if (uptime > 0.0) boot_time = time(NULL) - (time_t) uptime;

    return boot_time;
}

typedef struct {
    pid_t pid, ppid;
    int utime, stime, cutime, cstime;
    int psr, pri, nice, numthreads;
    long long int start_time, run_time;
    char state;
    unsigned int mem_rss, flags;
    unsigned long mem_virt;
    char name[1024];
} Stat;

static Eina_Bool
_stat(const char *path, Stat *st) {
    FILE *f;
    char *lparen, *rparen, *state;
    char line[4096];
    int dummy, len = 0;
    static long tck = 0;
    static int64_t boot_time = 0;

    if (!boot_time) boot_time = _boot_time();

    memset(st, 0, sizeof(Stat));

    f = fopen(path, "r");
    if (!f) return 0;

    if (fgets(line, sizeof(line), f)) {
        lparen = strchr(line, '(');
        rparen = strrchr(line, ')');
        if (!lparen || !rparen || (rparen <= lparen) || (rparen[1] != ' ') || !rparen[2]) {
            fclose(f);
            return 0;
        }

        state = rparen + 2;
        len = sscanf(state,
                     "%c %d %d %d %d %d %u %u %u %u %u %d %d %d"
                     " %d %d %d %u %u %lld %lu %u %u %u %u %u %u %u %d %d %d %d %u"
                     " %d %d %d %d %d %d %d %d %d",
                     &st->state, &st->ppid, &dummy, &dummy, &dummy, &dummy, &st->flags, &dummy, &dummy, &dummy, &dummy,
                     &st->utime, &st->stime, &st->cutime, &st->cstime, &st->pri, &st->nice, &st->numthreads, &dummy,
                     &st->start_time, &st->mem_virt, &st->mem_rss, &dummy, &dummy, &dummy, &dummy, &dummy, &dummy,
                     &dummy, &dummy, &dummy, &dummy, &dummy, &dummy, &dummy, &dummy, &st->psr, &dummy, &dummy, &dummy,
                     &dummy, &dummy);

        snprintf(st->name, sizeof(st->name), "%.*s", (int) (rparen - lparen - 1), lparen + 1);
    }
    fclose(f);

    if (len != 42) return 0;

    if (!tck) tck = sysconf(_SC_CLK_TCK);

    st->start_time /= tck;
    st->start_time += boot_time;
    st->run_time = (st->utime + st->stime) / tck;

    return 1;
}

static int
_n_files(Proc_Info *p) {
    char buf[256];
    Eina_List *files;
    char *f;

    snprintf(buf, sizeof(buf), "/proc/%d/fd", p->pid);

    files = ecore_file_ls(buf);
    EINA_LIST_FREE(files, f) {
        p->numfiles++;
        free(f);
    }
    return p->numfiles;
}

#if defined(__linux__)
typedef struct {
    unsigned long inode;
    uint64_t in;
    uint64_t out;
} Linux_Proc_Socket_Stat;

typedef struct {
    pid_t pid;
    uint64_t in;
    uint64_t out;
} Linux_Proc_Net_Stat;

typedef struct {
    unsigned long *inodes;
    int count;
    int capacity;
} Linux_Proc_Inode_Set;

static int
_linux_proc_socket_stat_cmp(const void *a, const void *b)
{
    const Linux_Proc_Socket_Stat *sa = a;
    const Linux_Proc_Socket_Stat *sb = b;

    if (sa->inode < sb->inode) return -1;
    if (sa->inode > sb->inode) return 1;
    return 0;
}

static Eina_Bool
_linux_proc_socket_stat_add(Linux_Proc_Socket_Stat **stats, int *count, int *capacity, unsigned long inode, uint64_t in,
                            uint64_t out)
{
    Linux_Proc_Socket_Stat *tmp;
    int next_capacity;

    if (*count >= *capacity) {
        next_capacity = *capacity ? *capacity * 2 : 256;
        tmp = realloc(*stats, next_capacity * sizeof(**stats));
        if (!tmp) return EINA_FALSE;
        *stats = tmp;
        *capacity = next_capacity;
    }

    (*stats)[*count].inode = inode;
    (*stats)[*count].in = in;
    (*stats)[*count].out = out;
    (*count)++;

    return EINA_TRUE;
}

static void
_linux_proc_socket_diag_collect(int family, Linux_Proc_Socket_Stat **stats, int *count, int *capacity)
{
    int fd;
    ssize_t sent;
    struct {
        struct nlmsghdr nlh;
        struct inet_diag_req_v2 req;
    } request;
    char buffer[8192];

    fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
    if (fd < 0) return;

    memset(&request, 0, sizeof(request));
    request.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(request.req));
    request.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
    request.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
    request.req.sdiag_family = family;
    request.req.sdiag_protocol = IPPROTO_TCP;
    request.req.idiag_states = -1;
    request.req.idiag_ext = (1 << (INET_DIAG_INFO - 1));

    sent = send(fd, &request, request.nlh.nlmsg_len, 0);
    if (sent < 0) {
        close(fd);
        return;
    }

    while (1) {
        ssize_t len = recv(fd, buffer, sizeof(buffer), 0);
        struct nlmsghdr *nlh;

        if (len <= 0) break;

        for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
            struct inet_diag_msg *diag;
            struct rtattr *attr;
            int attr_len;

            if (nlh->nlmsg_type == NLMSG_DONE) {
                close(fd);
                return;
            }
            if (nlh->nlmsg_type == NLMSG_ERROR) {
                close(fd);
                return;
            }
            if (nlh->nlmsg_type != SOCK_DIAG_BY_FAMILY) continue;
            if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*diag))) continue;

            diag = NLMSG_DATA(nlh);
            attr_len = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*diag));
            if (attr_len <= 0) continue;

            for (attr = (struct rtattr *) (diag + 1); RTA_OK(attr, attr_len); attr = RTA_NEXT(attr, attr_len)) {
                struct tcp_info *tcpi;
                size_t tcpi_len;
                uint64_t in = 0, out = 0;

                if (attr->rta_type != INET_DIAG_INFO) continue;

                tcpi = RTA_DATA(attr);
                tcpi_len = RTA_PAYLOAD(attr);

                if (tcpi_len >= (offsetof(struct tcp_info, tcpi_bytes_received) + sizeof(tcpi->tcpi_bytes_received)))
                    in = tcpi->tcpi_bytes_received;
                if (tcpi_len >= (offsetof(struct tcp_info, tcpi_bytes_acked) + sizeof(tcpi->tcpi_bytes_acked)))
                    out = tcpi->tcpi_bytes_acked;

                if ((!in) && (!out)) continue;
                if (!_linux_proc_socket_stat_add(stats, count, capacity, diag->idiag_inode, in, out)) {
                    close(fd);
                    return;
                }
            }
        }
    }

    close(fd);
}

static Linux_Proc_Socket_Stat *
_linux_proc_socket_stats_get(int *count)
{
    Linux_Proc_Socket_Stat *stats;
    int capacity = 0, out_count = 0;

    *count = 0;

    stats = NULL;
    _linux_proc_socket_diag_collect(AF_INET, &stats, &out_count, &capacity);
    _linux_proc_socket_diag_collect(AF_INET6, &stats, &out_count, &capacity);

    if (!out_count) return stats;

    qsort(stats, out_count, sizeof(*stats), _linux_proc_socket_stat_cmp);

    int write = 0;
    for (int read = 0; read < out_count; read++) {
        if ((write > 0) && (stats[read].inode == stats[write - 1].inode)) {
            stats[write - 1].in += stats[read].in;
            stats[write - 1].out += stats[read].out;
            continue;
        }
        if (write != read) stats[write] = stats[read];
        write++;
    }

    *count = write;
    return stats;
}

static const Linux_Proc_Socket_Stat *
_linux_proc_socket_stat_find(const Linux_Proc_Socket_Stat *stats, int count, unsigned long inode)
{
    Linux_Proc_Socket_Stat key;
    key.inode = inode;
    key.in = 0;
    key.out = 0;
    return bsearch(&key, stats, count, sizeof(*stats), _linux_proc_socket_stat_cmp);
}

static int
_linux_proc_inode_seen_add(Linux_Proc_Inode_Set *set, unsigned long inode)
{
    unsigned long *tmp;
    int next_capacity;

    for (int i = 0; i < set->count; i++) {
        if (set->inodes[i] == inode) return 0;
    }

    if (set->count >= set->capacity) {
        next_capacity = set->capacity ? set->capacity * 2 : 16;
        tmp = realloc(set->inodes, next_capacity * sizeof(*set->inodes));
        if (!tmp) return -1;
        set->inodes = tmp;
        set->capacity = next_capacity;
    }

    set->inodes[set->count++] = inode;
    return 1;
}

static Eina_Bool
_linux_proc_net_usage_add(Linux_Proc_Net_Stat ***procs, int *n, pid_t pid, uint64_t in, uint64_t out)
{
    Linux_Proc_Net_Stat **tmp;
    Linux_Proc_Net_Stat *proc;

    tmp = realloc(*procs, (*n + 1) * sizeof(**procs));
    if (!tmp) return EINA_FALSE;
    *procs = tmp;

    proc = calloc(1, sizeof(*proc));
    if (!proc) return EINA_FALSE;

    proc->pid = pid;
    proc->in = in;
    proc->out = out;

    (*procs)[*n] = proc;
    (*n)++;

    return EINA_TRUE;
}

static Linux_Proc_Net_Stat **
_linux_process_network_usage_get(int *n)
{
    DIR *dir;
    struct dirent *entry;
    Linux_Proc_Socket_Stat *sockets;
    int socket_count;
    Linux_Proc_Net_Stat **procs = NULL;

    *n = 0;

    sockets = _linux_proc_socket_stats_get(&socket_count);
    if ((!sockets) || (!socket_count)) {
        free(sockets);
        return NULL;
    }

    dir = opendir("/proc");
    if (!dir) {
        free(sockets);
        return NULL;
    }

    while ((entry = readdir(dir))) {
        char *end = NULL;
        unsigned long pid_ul;
        pid_t pid;
        char fd_dir[PATH_MAX];
        DIR *fd;
        struct dirent *fd_entry;
        uint64_t in = 0, out = 0;
        Linux_Proc_Inode_Set seen = { 0 };

        if (!isdigit((unsigned char) entry->d_name[0])) continue;

        pid_ul = strtoul(entry->d_name, &end, 10);
        if (!end || *end) continue;
        if (pid_ul > INT_MAX) continue;
        pid = (pid_t) pid_ul;

        snprintf(fd_dir, sizeof(fd_dir), "/proc/%d/fd", pid);
        fd = opendir(fd_dir);
        if (!fd) continue;

        while ((fd_entry = readdir(fd))) {
            char link_path[PATH_MAX];
            char target[256];
            ssize_t len;
            unsigned long inode;
            const Linux_Proc_Socket_Stat *sock;

            if (fd_entry->d_name[0] == '.') continue;

            size_t base_len = strlen(fd_dir);
            size_t name_len = strlen(fd_entry->d_name);
            if ((base_len + 1 + name_len + 1) > sizeof(link_path)) continue;
            memcpy(link_path, fd_dir, base_len);
            link_path[base_len] = '/';
            memcpy(link_path + base_len + 1, fd_entry->d_name, name_len);
            link_path[base_len + 1 + name_len] = '\0';
            len = readlink(link_path, target, sizeof(target) - 1);
            if (len <= 0) continue;

            target[len] = '\0';
            if (strncmp(target, "socket:[", 8)) continue;

            inode = strtoul(target + 8, &end, 10);
            if (!end || (*end != ']')) continue;
            if (_linux_proc_inode_seen_add(&seen, inode) <= 0) continue;

            sock = _linux_proc_socket_stat_find(sockets, socket_count, inode);
            if (!sock) continue;

            in += sock->in;
            out += sock->out;
        }

        closedir(fd);
        free(seen.inodes);

        if ((!in) && (!out)) continue;
        if (!_linux_proc_net_usage_add(&procs, n, pid, in, out)) break;
    }

    closedir(dir);
    free(sockets);

    return procs;
}

static void
_linux_process_network_usage_free(Linux_Proc_Net_Stat **procs, int n)
{
    if (!procs) return;
    for (int i = 0; i < n; i++) free(procs[i]);
    free(procs);
}

static void
_linux_process_network_usage_apply(Proc_Info *p, Eina_Hash *by_pid)
{
    int32_t k;
    Linux_Proc_Net_Stat *stat;

    if (!p) return;
    p->net_in = 0;
    p->net_out = 0;
    if (!by_pid) return;

    k = p->pid;
    stat = eina_hash_find(by_pid, &k);
    if (!stat) return;
    p->net_in = stat->in;
    p->net_out = stat->out;
}
#endif

static uint64_t
_disk_write_get(pid_t pid) {
    FILE *f;
    char path[PATH_MAX];
    char buf[4096];
    uint64_t write_bytes = 0;

    snprintf(path, sizeof(path), "/proc/%d/io", pid);
    f = fopen(path, "r");
    if (!f) return 0;

    while (fgets(buf, sizeof(buf), f)) {
        unsigned long long value = 0;
        if (sscanf(buf, "write_bytes: %llu", &value) == 1) {
            write_bytes = value;
            break;
        }
    }

    fclose(f);

    return write_bytes;
}

static uint64_t
_disk_read_get(pid_t pid) {
    FILE *f;
    char path[PATH_MAX];
    char buf[4096];
    uint64_t read_bytes = 0;

    snprintf(path, sizeof(path), "/proc/%d/io", pid);
    f = fopen(path, "r");
    if (!f) return 0;

    while (fgets(buf, sizeof(buf), f)) {
        unsigned long long value = 0;
        if (sscanf(buf, "read_bytes: %llu", &value) == 1) {
            read_bytes = value;
            break;
        }
    }

    fclose(f);

    return read_bytes;
}

static Eina_List *
_process_list_linux_get(void) {
    Eina_List *files, *list;
    const char *state;
    char *n;
    char buf[4096];
    Stat st;
#if defined(__linux__)
    Linux_Proc_Net_Stat **proc_net = NULL;
    Eina_Hash *proc_net_hash = NULL;
    int proc_net_count = 0;
#endif

    list = NULL;

#if defined(__linux__)
    proc_net = _linux_process_network_usage_get(&proc_net_count);
    if ((proc_net) && (proc_net_count > 0)) {
        proc_net_hash = eina_hash_int32_new(NULL);
        if (proc_net_hash) {
            for (int i = 0; i < proc_net_count; i++) {
                int32_t key = proc_net[i]->pid;
                eina_hash_add(proc_net_hash, &key, proc_net[i]);
            }
        }
    }
#endif

    files = ecore_file_ls("/proc");
    EINA_LIST_FREE(files, n) {
        pid_t pid = (pid_t) atoi(n);
        free(n);

        if (!pid) continue;

        snprintf(buf, sizeof(buf), "/proc/%d/stat", pid);
        if (!_stat(buf, &st)) continue;

        if (st.flags & PF_KTHREAD && !proc_info_kthreads_show_get()) continue;

        Proc_Info *p = calloc(1, sizeof(Proc_Info));
        if (!p) continue;

        p->pid = pid;
        p->ppid = st.ppid;
        p->uid = _uid(pid);
        p->cpu_id = st.psr;
        p->start = st.start_time;
        p->run_time = st.run_time;
        state = _process_state_name(st.state);
        snprintf(p->state, sizeof(p->state), "%s", state);
        p->cpu_time = st.utime + st.stime;
        p->nice = st.nice;
        p->priority = st.pri;
        p->numthreads = st.numthreads;
        p->numfiles = _n_files(p);
        if (st.flags & PF_KTHREAD) p->is_kernel = 1;
        _mem_size(p);
        _cmd_args(p, st.name, sizeof(st.name));
        _linux_process_network_usage_apply(p, proc_net_hash);
        p->disk_read = _disk_read_get(pid);
        p->disk_write = _disk_write_get(pid);

        Eina_List *next = eina_list_append(list, p);
        if (!next) {
            proc_info_free(p);
            continue;
        }
        list = next;
    }

#if defined(__linux__)
    if (proc_net_hash) eina_hash_free(proc_net_hash);
    _linux_process_network_usage_free(proc_net, proc_net_count);
#endif

    return list;
}

static void
_proc_thread_info(Proc_Info *p) {
    Eina_List *files;
    const char *state;
    char *n;
    char buf[4096];
    Stat st;

    snprintf(buf, sizeof(buf), "/proc/%d/task", p->pid);
    files = ecore_file_ls(buf);
    EINA_LIST_FREE(files, n) {
        int tid = atoi(n);
        free(n);
        snprintf(buf, sizeof(buf), "/proc/%d/task/%d/stat", p->pid, tid);
        if (!_stat(buf, &st)) continue;

        Proc_Info *t = calloc(1, sizeof(Proc_Info));
        if (!t) continue;
        t->cpu_id = st.psr;
        state = _process_state_name(st.state);
        snprintf(t->state, sizeof(t->state), "%s", state);
        t->cpu_time = st.utime + st.stime;
        t->nice = st.nice;
        t->priority = st.pri;
        t->numthreads = st.numthreads;
        t->mem_virt = st.mem_virt;
        t->mem_rss = st.mem_rss;
        t->net_in = 0;
        t->net_out = 0;
        t->disk_read = 0;
        t->disk_write = 0;

        t->tid = tid;
        t->thread_name = strdup(st.name);
        if (!t->thread_name) {
            proc_info_free(t);
            continue;
        }

        Eina_List *next = eina_list_append(p->threads, t);
        if (!next) {
            proc_info_free(t);
            continue;
        }
        p->threads = next;
    }
}

Proc_Info *
proc_info_by_pid(pid_t pid) {
    const char *state;
    Stat st;
    char buf[4096];

    snprintf(buf, sizeof(buf), "/proc/%d/stat", pid);
    if (!_stat(buf, &st)) return NULL;

    Proc_Info *p = calloc(1, sizeof(Proc_Info));
    if (!p) return NULL;

    p->pid = pid;
    p->ppid = st.ppid;
    p->uid = _uid(pid);
    p->cpu_id = st.psr;
    p->start = st.start_time;
    p->run_time = st.run_time;
    state = _process_state_name(st.state);
    snprintf(p->state, sizeof(p->state), "%s", state);
    p->cpu_time = st.utime + st.stime;
    p->priority = st.pri;
    p->nice = st.nice;
    p->numthreads = st.numthreads;
    p->numfiles = _n_files(p);
    if (st.flags & PF_KTHREAD) p->is_kernel = 1;
    _mem_size(p);
    _cmd_args(p, st.name, sizeof(st.name));
    {
        Linux_Proc_Net_Stat **proc_net = NULL;
        int proc_net_count = 0;
        proc_net = _linux_process_network_usage_get(&proc_net_count);
        if ((proc_net) && (proc_net_count > 0)) {
            for (int i = 0; i < proc_net_count; i++) {
                if (proc_net[i]->pid != pid) continue;
                p->net_in = proc_net[i]->in;
                p->net_out = proc_net[i]->out;
                break;
            }
        }
        _linux_process_network_usage_free(proc_net, proc_net_count);
    }
    p->disk_read = _disk_read_get(pid);
    p->disk_write = _disk_write_get(pid);

    _proc_thread_info(p);

    return p;
}

#endif

#if defined(__OpenBSD__)

static void
_proc_get(Proc_Info *p, struct kinfo_proc *kp) {
    static int pagesize = 0;
    const char *state;

    if (!pagesize) pagesize = getpagesize();

    p->pid = kp->p_pid;
    p->ppid = kp->p_ppid;
    p->uid = kp->p_uid;
    p->cpu_id = kp->p_cpuid;
    p->start = kp->p_ustart_sec;
    p->run_time = kp->p_uutime_sec + kp->p_ustime_sec + (kp->p_uutime_usec / 1000000) + (kp->p_ustime_usec / 1000000);

    state = _process_state_name(kp->p_stat);
    snprintf(p->state, sizeof(p->state), "%s", state);
    snprintf(p->wchan, sizeof(p->wchan), "%s", kp->p_wmesg);
    p->cpu_time = kp->p_uticks + kp->p_sticks + kp->p_iticks;
    p->mem_virt = p->mem_size = (MEMSIZE(kp->p_vm_tsize) * MEMSIZE(pagesize))
                                + (MEMSIZE(kp->p_vm_dsize) * MEMSIZE(pagesize))
                                + (MEMSIZE(kp->p_vm_ssize) * MEMSIZE(pagesize));
    p->mem_rss = MEMSIZE(kp->p_vm_rssize) * MEMSIZE(pagesize);
    p->priority = kp->p_priority - PZERO;
    p->nice = kp->p_nice - NZERO;
    p->tid = kp->p_tid;
    p->net_in = 0;
    p->net_out = 0;
    p->disk_read = 0;
    p->disk_write = 0;
}

static void
_kvm_get(Proc_Info *p, kvm_t *kern, struct kinfo_proc *kp) {
    char **args;
    char name[4096];

    if ((args = kvm_getargv(kern, kp, sizeof(name) - 1))) {
        Eina_Strbuf *buf = eina_strbuf_new();
        if (!buf) goto args_done;
        for (int i = 0; args[i]; i++) {
            eina_strbuf_append(buf, args[i]);
            if (args[i + 1]) eina_strbuf_append(buf, " ");
        }
        p->arguments = eina_strbuf_string_steal(buf);
        eina_strbuf_free(buf);

        if (args[0] && ecore_file_exists(args[0])) p->command = strdup(ecore_file_file_get(args[0]));
    }
args_done:
    struct kinfo_file *kf;
    int n;

    if ((kf = kvm_getfiles(kern, KERN_FILE_BYPID, -1, sizeof(struct kinfo_file), &n))) {
        for (int i = 0; i < n; i++) {
            if (kf[i].p_pid == kp->p_pid) {
                if (kf[i].fd_fd >= 0) p->numfiles++;
            }
        }
    }

    if (!p->command) p->command = strdup(kp->p_comm);
    if (!p->command) p->command = strdup("");
}

Proc_Info *
proc_info_by_pid(pid_t pid) {
    struct kinfo_proc *kp, *kpt;
    kvm_t *kern;
    char errbuf[_POSIX2_LINE_MAX];
    int count, pid_count;

    kern = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
    if (!kern) return NULL;

    kp = kvm_getprocs(kern, KERN_PROC_PID, pid, sizeof(*kp), &count);
    if (!kp) {
        kvm_close(kern);
        return NULL;
    }

    if (count == 0) {
        kvm_close(kern);
        return NULL;
    }

    Proc_Info *p = calloc(1, sizeof(Proc_Info));
    if (!p) {
        kvm_close(kern);
        return NULL;
    }

    _proc_get(p, kp);
    _kvm_get(p, kern, kp);

    kp = kvm_getprocs(kern, KERN_PROC_SHOW_THREADS, 0, sizeof(*kp), &pid_count);

    for (int i = 0; i < pid_count; i++) {
        if (kp[i].p_pid != p->pid) continue;

        kpt = &kp[i];

        if (kpt->p_tid <= 0) continue;

        Proc_Info *t = calloc(1, sizeof(Proc_Info));
        if (!t) continue;

        _proc_get(t, kpt);

        t->tid = kpt->p_tid;
        t->thread_name = strdup(kpt->p_comm);
        if (!t->thread_name) {
            proc_info_free(t);
            continue;
        }
        if (!t->thread_name) {
            proc_info_free(t);
            continue;
        }

        Eina_List *next = eina_list_append(p->threads, t);
        if (!next) {
            proc_info_free(t);
            continue;
        }
        p->threads = next;
    }

    p->numthreads = eina_list_count(p->threads);

    kvm_close(kern);

    return p;
}

static Eina_List *
_process_list_openbsd_get(void) {
    struct kinfo_proc *kps, *kp;
    Proc_Info *p;
    char errbuf[4096];
    kvm_t *kern;
    int pid_count;
    Eina_List *list = NULL;

    kern = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
    if (!kern) return NULL;

    kps = kvm_getprocs(kern, KERN_PROC_ALL, 0, sizeof(*kps), &pid_count);
    if (!kps) {
        kvm_close(kern);
        return NULL;
    }

    for (int i = 0; i < pid_count; i++) {
        kp = &kps[i];

        p = proc_info_by_pid(kp->p_pid);
        if (p) {
            Eina_List *next = eina_list_append(list, p);
            if (!next) proc_info_free(p);
            else list = next;
        }
    }

    kvm_close(kern);

    return list;
}

#endif

#if defined(__MacOS__)
static void
_cmd_get(Proc_Info *p, pid_t pid) {
    char *cp, *args = NULL, **argv = NULL;
    int mib[3], argmax, argc;
    size_t size;
    Eina_Strbuf *buf = NULL;

    mib[0] = CTL_KERN;
    mib[1] = KERN_ARGMAX;

    size = sizeof(argmax);

    if (sysctl(mib, 2, &argmax, &size, NULL, 0) == -1) return;

    mib[0] = CTL_KERN;
    mib[1] = KERN_PROCARGS2;
    mib[2] = pid;

    size = (size_t) argmax;
    args = malloc(argmax);
    if (!args) return;

    /* See libtop.c (top) for the origin of this comment, which is necessary as
     * there is little other documentation...thanks Apple.
     *
     * Make a sysctl() call to get the raw argument space of the process.
     * The layout is documented in start.s, which is part of the Csu
     * project.  In summary, it looks like:
     *
     * /---------------\ 0x00000000
     * :               :
     * :               :
     * |---------------|
     * | argc          |
     * |---------------|
     * | arg[0]        |
     * |---------------|
     * :               :
     * :               :
     * |---------------|
     * | arg[argc - 1] |
     * |---------------|
     * | 0             |
     * |---------------|
     * | env[0]        |
     * |---------------|
     * :               :
     * :               :
     * |---------------|
     * | env[n]        |
     * |---------------|
     * | 0             |
     * |---------------| <-- Beginning of data returned by sysctl() is here.
     * | argc          |
     * |---------------|
     * | exec_path     |
     * |:::::::::::::::|
     * |               |
     * | String area.  |
     * |               |
     * |---------------| <-- Top of stack.
     * :               :
     * :               :
     * \---------------/ 0xffffffff
     */

    if (sysctl(mib, 3, args, &size, NULL, 0) == -1) goto done;

    memcpy(&argc, args, sizeof(argc));
    cp = args + sizeof(argc);

    /* Skip exec path */
    for (; cp < &args[size]; cp++) {
        if (*cp == '\0') break;
    }

    if (cp == &args[size]) goto done;

    /* Skip any padded NULLs. */
    for (; cp < &args[size]; cp++) {
        if (*cp == '\0') break;
    }

    if (cp == &args[size]) goto done;

    argv = malloc((1 + argc) * sizeof(char *));
    if (!argv) goto done;

    int i = 0;
    argv[i] = cp;

    for (cp = args + sizeof(int); cp < &args[size] && i < argc; cp++) {
        if (*cp == '\0') {
            while (*cp == '\0') cp++;
            argv[i++] = cp;
        }
    }

    if (i == 0) i++;

    argv[i] = NULL;

    if (argv[0]) {
        p->command = strdup(basename(argv[0]));
        if (!p->command) p->command = strdup("");
    }

    buf = eina_strbuf_new();
    if (!buf) goto done;

    for (i = 0; i < argc; i++) eina_strbuf_append_printf(buf, "%s ", argv[i]);

    if (argc > 0) {
        p->arguments = eina_strbuf_release(buf);
        buf = NULL;
    } else {
        eina_strbuf_free(buf);
        buf = NULL;
    }

done:
    if (buf) eina_strbuf_free(buf);
    free(args);
    free(argv);
}

static Proc_Info *
_proc_pidinfo(pid_t pid) {
    const char *state;
    struct proc_taskallinfo taskinfo;
    int size = proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, &taskinfo, sizeof(taskinfo));
    if (size != sizeof(taskinfo)) return NULL;

    Proc_Info *p = calloc(1, sizeof(Proc_Info));
    if (!p) return NULL;

    p->pid = pid;
    p->ppid = taskinfo.pbsd.pbi_ppid;
    p->uid = taskinfo.pbsd.pbi_uid;
    p->cpu_id = -1;
    p->cpu_time = taskinfo.ptinfo.pti_total_user + taskinfo.ptinfo.pti_total_system;
    p->cpu_time /= 10000000;
    p->start = taskinfo.pbsd.pbi_start_tvsec;
    state = _process_state_name(taskinfo.pbsd.pbi_status);
    snprintf(p->state, sizeof(p->state), "%s", state);
    p->mem_size = p->mem_virt = taskinfo.ptinfo.pti_virtual_size;
    p->mem_rss = taskinfo.ptinfo.pti_resident_size;
    p->priority = taskinfo.ptinfo.pti_priority;
    p->nice = taskinfo.pbsd.pbi_nice;
    p->numthreads = taskinfo.ptinfo.pti_threadnum;
    p->net_in = 0;
    p->net_out = 0;
    p->disk_read = 0;
    p->disk_write = 0;
    _cmd_get(p, pid);

    return p;
}

static Eina_List *
_process_list_macos_fallback_get(void) {
    Eina_List *list = NULL;

    for (int i = 1; i <= PID_MAX; i++) {
        Proc_Info *p = _proc_pidinfo(i);
        if (p) {
            Eina_List *next = eina_list_append(list, p);
            if (!next) proc_info_free(p);
            else list = next;
        }
    }

    return list;
}

static Eina_List *
_process_list_macos_get(void) {
    Eina_List *list = NULL;
    pid_t *pids = NULL;
    int size, pid_count;

    size = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
    if (size == -1) return _process_list_macos_fallback_get();

    pids = malloc(size * sizeof(pid_t));
    if (!pids) return NULL;

    size = proc_listpids(PROC_ALL_PIDS, 0, pids, size * sizeof(pid_t));
    if (size == -1) {
        free(pids);
        return _process_list_macos_fallback_get();
    }

    pid_count = size / sizeof(pid_t);
    for (int i = 0; i < pid_count; i++) {
        pid_t pid = pids[i];
        Proc_Info *p = _proc_pidinfo(pid);
        if (p) {
            Eina_List *next = eina_list_append(list, p);
            if (!next) proc_info_free(p);
            else list = next;
        }
    }

    free(pids);

    return list;
}

Proc_Info *
proc_info_by_pid(pid_t pid) {
    const char *state;
    struct proc_taskallinfo taskinfo;
    struct proc_workqueueinfo workqueue;
    size_t size;

    size = proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, &taskinfo, sizeof(taskinfo));
    if (size != sizeof(taskinfo)) return NULL;

    size = proc_pidinfo(pid, PROC_PIDWORKQUEUEINFO, 0, &workqueue, sizeof(workqueue));
    if (size != sizeof(workqueue)) memset(&workqueue, 0, sizeof(struct proc_workqueueinfo));

    Proc_Info *p = calloc(1, sizeof(Proc_Info));
    if (!p) return NULL;

    p->pid = pid;
    p->uid = taskinfo.pbsd.pbi_uid;
    p->ppid = taskinfo.pbsd.pbi_ppid;
    p->cpu_id = workqueue.pwq_nthreads;
    p->cpu_time = taskinfo.ptinfo.pti_total_user + taskinfo.ptinfo.pti_total_system;
    p->cpu_time /= 10000000;
    p->start = taskinfo.pbsd.pbi_start_tvsec;
    state = _process_state_name(taskinfo.pbsd.pbi_status);
    snprintf(p->state, sizeof(p->state), "%s", state);
    p->mem_size = p->mem_virt = taskinfo.ptinfo.pti_virtual_size;
    p->mem_rss = taskinfo.ptinfo.pti_resident_size;
    p->priority = taskinfo.ptinfo.pti_priority;
    p->nice = taskinfo.pbsd.pbi_nice;
    p->numthreads = taskinfo.ptinfo.pti_threadnum;
    p->net_in = 0;
    p->net_out = 0;
    p->disk_read = 0;
    p->disk_write = 0;
    _cmd_get(p, pid);

    return p;
}

#endif

#if defined(__FreeBSD__) || defined(__DragonFly__)

static int
_pid_max(void) {
    size_t len;
    static int pid_max = 0;

    if (pid_max != 0) return pid_max;

    len = sizeof(pid_max);
    if (sysctlbyname("kern.pid_max", &pid_max, &len, NULL, 0) == -1) {
#if defined(__FreeBSD__)
        pid_max = 99999;
#elif defined(__DragonFly__)
        pid_max = 999999;
#else
        pid_max = PID_MAX;
#endif
    }

    return pid_max;
}

static void
_kvm_get(Proc_Info *p, struct kinfo_proc *kp) {
    kvm_t *kern;
    char name[4096];
    Eina_Bool have_command = 0;

    kern = kvm_open(NULL, "/dev/mem", NULL, O_RDONLY, "kvm_open");
    if (!kern) goto nokvm;
    char **args;
    if ((args = kvm_getargv(kern, kp, sizeof(name) - 1)) && (args[0])) {
        char *base = strdup(args[0]);
        if (base) {
            char *spc = strchr(base, ' ');
            if (spc) *spc = '\0';

            if (ecore_file_exists(base)) {
                snprintf(name, sizeof(name), "%s", basename(base));
                have_command = 1;
            }
            free(base);
        }
        Eina_Strbuf *buf = eina_strbuf_new();
        if (!buf) goto done_args;
        for (int i = 0; args[i] != NULL; i++) {
            eina_strbuf_append(buf, args[i]);
            if (args[i + 1]) eina_strbuf_append(buf, " ");
        }
        p->arguments = eina_strbuf_string_steal(buf);
        eina_strbuf_free(buf);
    }
done_args:

    struct filedesc filed;
    struct fdescenttbl *fdt;
    unsigned int n;

    if (!kvm_read(kern, (unsigned long) kp->ki_fd, &filed, sizeof(filed))) goto kvmerror;

    if (!kvm_read(kern, (unsigned long) filed.fd_files, &n, sizeof(n))) goto kvmerror;

    unsigned int size = sizeof(*fdt) + n * sizeof(struct filedescent);
    fdt = malloc(size);
    if (fdt) {
        if (kvm_read(kern, (unsigned long) filed.fd_files, fdt, size)) {
            for (int i = 0; i < n; i++) {
                if (!fdt->fdt_ofiles[i].fde_file) continue;
                p->numfiles++;
            }
        }
        free(fdt);
    }

kvmerror:
    if (kern) kvm_close(kern);
nokvm:
    if (!have_command) snprintf(name, sizeof(name), "%s", kp->ki_comm);

    p->command = strdup(name);
    if (!p->command) p->command = strdup(kp->ki_comm);
}

static Proc_Info *
_proc_thread_info(struct kinfo_proc *kp, Eina_Bool is_thread) {
    struct rusage *usage;
    const char *state;
    Proc_Info *p;
    static int pagesize = 0;

    if (!pagesize) pagesize = getpagesize();

    p = calloc(1, sizeof(Proc_Info));
    if (!p) return NULL;

    p->pid = kp->ki_pid;
    p->ppid = kp->ki_ppid;
    p->uid = kp->ki_uid;

    if (!is_thread) _kvm_get(p, kp);

    p->cpu_id = kp->ki_oncpu;
    if (p->cpu_id == -1) p->cpu_id = kp->ki_lastcpu;

    usage = &kp->ki_rusage;

    p->cpu_time = ((usage->ru_utime.tv_sec * 1000000) + usage->ru_utime.tv_usec + (usage->ru_stime.tv_sec * 1000000)
                   + usage->ru_stime.tv_usec)
                  / 10000;
    p->run_time = (kp->ki_runtime + 500000) / 1000000;
    state = _process_state_name(kp->ki_stat);
    snprintf(p->state, sizeof(p->state), "%s", state);
    snprintf(p->wchan, sizeof(p->wchan), "%s", kp->ki_wmesg);
    p->mem_virt = kp->ki_size;
    p->mem_rss = MEMSIZE(kp->ki_rssize) * MEMSIZE(pagesize);
    p->start = kp->ki_start.tv_sec;
    p->mem_size = p->mem_virt;
    p->nice = kp->ki_nice - NZERO;
    p->priority = kp->ki_pri.pri_level - PZERO;
    p->numthreads = kp->ki_numthreads;
    p->net_in = 0;
    p->net_out = 0;
    p->disk_read = 0;
    p->disk_write = 0;

    p->tid = kp->ki_tid;
    p->thread_name = strdup(kp->ki_tdname);
    if (!p->thread_name) p->thread_name = strdup("");
    if (kp->ki_flag & P_KPROC) p->is_kernel = 1;

    return p;
}

static Eina_List *
_process_list_freebsd_fallback_get(void) {
    Eina_List *list;
    struct kinfo_proc kp;
    int mib[4];
    size_t len;
    static int pid_max;

    pid_max = _pid_max();

    list = NULL;

    len = sizeof(int);
    if (sysctlnametomib("kern.proc.pid", mib, &len) == -1) return NULL;

    for (int i = 1; i <= pid_max; i++) {
        mib[3] = i;
        len = sizeof(kp);
        if (sysctl(mib, 4, &kp, &len, NULL, 0) == -1) continue;

        if (kp.ki_flag & P_KPROC && !proc_info_kthreads_show_get()) continue;

        Proc_Info *p = _proc_thread_info(&kp, 0);
        if (p) {
            Eina_List *next = eina_list_append(list, p);
            if (!next) proc_info_free(p);
            else list = next;
        }
    }

    return list;
}

static Eina_List *
_process_list_freebsd_get(void) {
    kvm_t *kern;
    Eina_List *list = NULL;
    struct kinfo_proc *kps, *kp;
    char errbuf[_POSIX2_LINE_MAX];
    int pid_count;

    kern = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
    if (!kern) return _process_list_freebsd_fallback_get();

    kps = kvm_getprocs(kern, KERN_PROC_PROC, 0, &pid_count);
    if (!kps) {
        kvm_close(kern);
        return _process_list_freebsd_fallback_get();
    }

    for (int i = 0; i < pid_count; i++) {
        if (kps[i].ki_flag & P_KPROC && !proc_info_kthreads_show_get()) continue;

        kp = &kps[i];

        Proc_Info *p = _proc_thread_info(kp, 0);
        if (p) {
            Eina_List *next = eina_list_append(list, p);
            if (!next) proc_info_free(p);
            else list = next;
        }
    }

    kvm_close(kern);

    return list;
}

static Proc_Info *
_proc_info_by_pid_fallback(pid_t pid) {
    struct kinfo_proc kp;
    int mib[4];
    size_t len;

    len = sizeof(int);
    if (sysctlnametomib("kern.proc.pid", mib, &len) == -1) return NULL;

    mib[3] = pid;

    len = sizeof(kp);
    if (sysctl(mib, 4, &kp, &len, NULL, 0) == -1) return NULL;

    Proc_Info *p = _proc_thread_info(&kp, 0);

    return p;
}

Proc_Info *
proc_info_by_pid(pid_t pid) {
    kvm_t *kern;
    struct kinfo_proc *kps, *kp;
    char errbuf[_POSIX2_LINE_MAX];
    int pid_count;

    kern = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
    if (!kern) return _proc_info_by_pid_fallback(pid);

    kps = kvm_getprocs(kern, KERN_PROC_ALL, 0, &pid_count);
    if (!kps) {
        kvm_close(kern);
        return _proc_info_by_pid_fallback(pid);
    }

    Proc_Info *p = NULL;

    // XXX: run_time does not include interrupts.

    for (int i = 0; i < pid_count; i++) {
        if (kps[i].ki_flag & P_KPROC && !proc_info_kthreads_show_get()) continue;
        if (kps[i].ki_pid != pid) continue;

        kp = &kps[i];
        Proc_Info *t = _proc_thread_info(kp, 1);
        if (!t) continue;
        if (!p) {
            p = _proc_thread_info(kp, 0);
            if (!p) {
                proc_info_free(t);
                continue;
            }
            p->cpu_time = 0;
        }

        p->cpu_time += t->cpu_time;
        Eina_List *next = eina_list_append(p->threads, t);
        if (!next) {
            proc_info_free(t);
            continue;
        }
        p->threads = next;
    }

    kvm_close(kern);

    if (!p) return _proc_info_by_pid_fallback(pid);

    return p;
}
#endif

void
proc_info_free(Proc_Info *proc) {
    Proc_Info *t;

    if (!proc) return;

    EINA_LIST_FREE(proc->threads, t)
    proc_info_free(t);

    if (proc->command) free(proc->command);
    if (proc->arguments) free(proc->arguments);
    if (proc->thread_name) free(proc->thread_name);

    free(proc);
}

Eina_List *
proc_info_all_get(void) {
    Eina_List *processes;

#if defined(__linux__)
    processes = _process_list_linux_get();
#elif defined(__FreeBSD__) || defined(__DragonFly__)
    processes = _process_list_freebsd_get();
#elif defined(__MacOS__)
    processes = _process_list_macos_get();
#elif defined(__OpenBSD__)
    processes = _process_list_openbsd_get();
#else
    processes = NULL;
#endif

    return processes;
}

static Eina_Bool
_child_add(Eina_List *parents, Proc_Info *child) {
    Eina_List *l;
    Proc_Info *parent;

    EINA_LIST_FOREACH(parents, l, parent) {
        if (parent->pid == child->ppid) {
            Eina_List *next = eina_list_append(parent->children, child);
            if (!next) return 0;
            parent->children = next;
            return 1;
        }
    }

    return 0;
}

Eina_List *
proc_info_all_children_get() {
    Proc_Info *proc;
    Eina_List *l;
    Eina_List *procs;

    procs = proc_info_all_get();

    EINA_LIST_FOREACH(procs, l, proc) {
        int ok = _child_add(procs, proc);
        (void) ok;
    }

    return procs;
}

Eina_List *
_append_wanted(Eina_List *wanted, Eina_List *tree) {
    Eina_List *l;
    Proc_Info *parent;

    EINA_LIST_FOREACH(tree, l, parent) {
        wanted = eina_list_append(wanted, parent);
        if (!wanted) return NULL;
        if (parent->children) wanted = _append_wanted(wanted, parent->children);
    }
    return wanted;
}

Eina_List *
proc_info_pid_children_get(pid_t pid) {
    Proc_Info *proc;
    Eina_List *l, *procs, *wanted = NULL;

    procs = proc_info_all_children_get();

    EINA_LIST_FOREACH(procs, l, proc) {
        if (!wanted && proc->pid == pid) {
            wanted = eina_list_append(wanted, proc);
            if (!wanted) break;
            if (proc->children) wanted = _append_wanted(wanted, proc->children);
        }
    }

    EINA_LIST_FREE(procs, proc) {
        if (!eina_list_data_find(wanted, proc)) {
            proc_info_free(proc);
        }
    }

    return wanted;
}

void
proc_info_all_children_free(Eina_List *pstree) {
    Proc_Info *parent, *child;

    EINA_LIST_FREE(pstree, parent) {
        EINA_LIST_FREE(parent->children, child)
        proc_info_pid_children_free(child);
        proc_info_free(parent);
    }
}

void
proc_info_pid_children_free(Proc_Info *proc) {
    Proc_Info *child;

    EINA_LIST_FREE(proc->children, child)
    proc_info_free(child);

    proc_info_free(proc);
}

int
proc_sort_by_pid(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    return inf1->pid - inf2->pid;
}

int
proc_sort_by_uid(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    return inf1->uid - inf2->uid;
}

int
proc_sort_by_nice(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    return inf1->nice - inf2->nice;
}

int
proc_sort_by_pri(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    return inf1->priority - inf2->priority;
}

int
proc_sort_by_cpu(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    return inf1->cpu_id - inf2->cpu_id;
}

int
proc_sort_by_threads(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    return inf1->numthreads - inf2->numthreads;
}

int
proc_sort_by_files(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    return inf1->numfiles - inf2->numfiles;
}

int
proc_sort_by_size(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;
    int64_t size1, size2;

    inf1 = p1;
    inf2 = p2;

    size1 = inf1->mem_size;
    size2 = inf2->mem_size;

    if (size1 > size2) return 1;
    if (size1 < size2) return -1;

    return 0;
}

int
proc_sort_by_virt(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;
    int64_t size1, size2;

    inf1 = p1;
    inf2 = p2;

    size1 = inf1->mem_virt;
    size2 = inf2->mem_virt;

    if (size1 > size2) return 1;
    if (size1 < size2) return -1;

    return 0;
}

int
proc_sort_by_rss(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;
    int64_t size1, size2;

    inf1 = p1;
    inf2 = p2;

    size1 = inf1->mem_rss;
    size2 = inf2->mem_rss;

    if (size1 > size2) return 1;
    if (size1 < size2) return -1;

    return 0;
}

int
proc_sort_by_shared(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;
    int64_t size1, size2;

    inf1 = p1;
    inf2 = p2;

    size1 = inf1->mem_shared;
    size2 = inf2->mem_shared;

    if (size1 > size2) return 1;
    if (size1 < size2) return -1;

    return 0;
}

int
proc_sort_by_net_in(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    if (inf1->net_in > inf2->net_in) return 1;
    if (inf1->net_in < inf2->net_in) return -1;

    return 0;
}

int
proc_sort_by_net_out(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    if (inf1->net_out > inf2->net_out) return 1;
    if (inf1->net_out < inf2->net_out) return -1;

    return 0;
}

int
proc_sort_by_disk_read(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    if (inf1->disk_read > inf2->disk_read) return 1;
    if (inf1->disk_read < inf2->disk_read) return -1;

    return 0;
}

int
proc_sort_by_disk_write(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    if (inf1->disk_write > inf2->disk_write) return 1;
    if (inf1->disk_write < inf2->disk_write) return -1;

    return 0;
}

int
proc_sort_by_time(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;
    int64_t t1, t2;

    inf1 = p1;
    inf2 = p2;

    t1 = inf1->run_time;
    t2 = inf2->run_time;

    if (t1 > t2) return 1;
    if (t1 < t2) return -1;

    return 0;
}

int
proc_sort_by_cpu_usage(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;
    double one, two;

    inf1 = p1;
    inf2 = p2;

    one = inf1->cpu_usage;
    two = inf2->cpu_usage;

    if (one > two) return 1;
    else if (one < two) return -1;
    else return 0;
}

int
proc_sort_by_cmd(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    return strcasecmp(inf1->command, inf2->command);
}

int
proc_sort_by_state(const void *p1, const void *p2) {
    const Proc_Info *inf1, *inf2;

    inf1 = p1;
    inf2 = p2;

    return strcmp(inf1->state, inf2->state);
}

int
proc_sort_by_age(const void *p1, const void *p2) {
    const Proc_Info *c1 = p1, *c2 = p2;

    return c1->start - c2->start;
}
