#include <net-snmp/net-snmp-config.h>

#include "get_pid_from_inode.h"

#include <net-snmp/library/system.h> /* strlcpy() */
#include <net-snmp/output_api.h>

#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif

# define PROC_PATH          "/proc"
# define SOCKET_TYPE_1      "socket:["
# define SOCKET_TYPE_2      "[0000]:"

/* Definition of a simple open addressing hash table.*/
/* When inode == 0 then the entry is empty.*/
typedef struct {
    ino64_t inode;
    pid_t   pid;
} inode_pid_ent_t;

#define INODE_PID_TABLE_MAX_COLLISIONS 1000
#define INODE_PID_TABLE_LENGTH 20000
#define INODE_PID_TABLE_SIZE (INODE_PID_TABLE_LENGTH * sizeof (inode_pid_ent_t))
static inode_pid_ent_t  inode_pid_table[INODE_PID_TABLE_LENGTH];

static uint32_t
_hash(uint64_t key)
{
    key = (~key) + (key << 18);
    key = key ^ (key >> 31);
    key = key * 21;
    key = key ^ (key >> 11);
    key = key + (key << 6);
    key = key ^ (key >> 22);
    return key;
}

static void
_clear(void)
{
    /* Clear the inode/pid hash table.*/
    memset(inode_pid_table, 0, INODE_PID_TABLE_SIZE);
}

static void
_set(ino64_t inode, pid_t pid)
{
    uint32_t        hash = _hash(inode);
    uint32_t        i;
    inode_pid_ent_t *entry;

    /* We will try for a maximum number of collisions.*/
    for (i = 0; i < INODE_PID_TABLE_MAX_COLLISIONS; i++) {
        entry = &inode_pid_table[(hash + i) % INODE_PID_TABLE_LENGTH];

        /* Check if this entry is empty, or the actual inode we were looking for.*/
        /* The second part should never happen, but it is here for completeness.*/
        if (entry->inode == 0 || entry->inode == inode) {
            entry->inode = inode;
            entry->pid = pid;
            return;
        }
    }

    /* We will silently fail to insert the inode if we get too many collisions.*/
    /* the _get function will return a zero pid.*/
}

static pid_t _get(ino64_t inode)
{
    uint32_t        hash = _hash(inode);
    uint32_t        i;
    inode_pid_ent_t *entry;

    /* We will try for a maximum number of collisions.*/
    for (i = 0; i < INODE_PID_TABLE_MAX_COLLISIONS; i++) {
        entry = &inode_pid_table[(hash + i) % INODE_PID_TABLE_LENGTH];

        /* Check if this entry is empty, or the actual inode we were looking for.*/
        /* If the entry is empty it means the inode is not in the table and we*/
        /* should return 0, the entry will also have a zero pid.*/
        if (entry->inode == 0 || entry->inode == inode) {
            return entry->pid;
        }
    }

    /* We could not find the pid.*/
    return 0;
}

void
netsnmp_get_pid_from_inode_init(void)
{
    DIR            *procdirs = NULL, *piddirs = NULL;
    char            path_name[PATH_MAX + 1];
    char            socket_lnk[NAME_MAX + 1];
    int             filelen = 0, readlen = 0;
    struct dirent  *procinfo, *pidinfo;
    pid_t           pid = 0;
    ino64_t         temp_inode;

    _clear();

    /* walk over all directories in /proc*/
    if (!(procdirs = opendir(PROC_PATH))) {
        NETSNMP_LOGONCE((LOG_ERR, "snmpd: cannot open /proc\n"));
        return;
    }

    while ((procinfo = readdir(procdirs)) != NULL) {
        const char* name = procinfo->d_name;

        /* A pid directory only contains digits, check for those.*/
        for (; *name; name++) {
            if (!isdigit(*name))
                break;
        }
        if(*name)
            continue;

        /* Create the /proc/<pid>/fd/ path name.*/
        memset(path_name, '\0', PATH_MAX + 1);
        filelen = snprintf(path_name, PATH_MAX,
                           PROC_PATH "/%s/fd/", procinfo->d_name);
        if (filelen <= 0 || PATH_MAX < filelen)
            continue;

        /* walk over all the files in /proc/<pid>/fd/*/
        if (!(piddirs = opendir(path_name)))
        continue;

        while ((pidinfo = readdir(piddirs)) != NULL) {
            if (filelen + strlen(pidinfo->d_name) > PATH_MAX)
                continue;

            strlcpy(path_name + filelen, pidinfo->d_name,
                    sizeof(path_name) - filelen);

            /* The file discriptor is a symbolic link to a socket or a file.*/
            /* Thus read the symbolic link.*/
            memset(socket_lnk, '\0', NAME_MAX + 1);
            readlen = readlink(path_name, socket_lnk, NAME_MAX);
            if (readlen < 0)
                continue;

            socket_lnk[readlen] = '\0';

            /* Check if to see if the file descriptor is a socket by comparing*/
            /* the start to a string. Also extract the inode number from this*/
            /* symbolic link.*/
            if (!strncmp(socket_lnk, SOCKET_TYPE_1, 8)) {
                temp_inode = strtoull(socket_lnk + 8, NULL, 0);
            } else if (!strncmp(socket_lnk, SOCKET_TYPE_2, 7)) {
                temp_inode = strtoull(socket_lnk + 7, NULL, 0);
            } else {
                temp_inode = 0;
            }

            /* Add the inode/pid combination to our hash table.*/
            if (temp_inode != 0) {
                pid = strtoul(procinfo->d_name, NULL, 0);
                _set(temp_inode, pid);
            }
        }
        closedir(piddirs);
    }
    if (procdirs)
        closedir(procdirs);
}

pid_t
netsnmp_get_pid_from_inode(ino64_t inode)
{
    return _get(inode);
}

