/*
 * fuser.c - identify processes using files
 *
 * Based on fuser.c Copyright (C) 1993-2005 Werner Almesberger and Craig Small
 *
 * Completely re-written
 * Copyright (C) 2005-2022 Craig Small <csmall@dropbear.xyz>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/sysmacros.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pwd.h>
#include <netdb.h>
#include <dirent.h>
#include <unistd.h>
#include <ctype.h>
#include <mntent.h>
#include <signal.h>
#include <getopt.h>
#include <setjmp.h>
#include <limits.h>
/* MAXSYMLINKS is a BSDism.  If it doesn't exist, fall back to SYMLINK_MAX,
   which is the POSIX name. */
#ifndef MAXSYMLINKS
#define MAXSYMLINKS SYMLINK_MAX
#endif

#ifdef ENABLE_NLS
#include <locale.h>
#endif

#include "fuser.h"
#include "signals.h"
#include "i18n.h"
#include "timeout.h"
#include "comm.h"

//#define DEBUG 1

#ifndef PATH_MAX
#define PATH_MAX 4096
#endif /* PATH_MAX */

#define NAME_FIELD 20        /* space reserved for file name */
/* Function defines */
static void add_matched_proc(struct names *name_list, const pid_t pid,
                             const uid_t uid, const char access);
static void add_special_proc(struct names *name_list, const char ptype,
                             const uid_t uid, const char *command);
static void check_dir(const pid_t pid, const char *dirname,
                      struct device_list *dev_head,
                      struct inode_list *ino_head, const uid_t uid,
                      const char access, struct unixsocket_list *sockets,
                      dev_t netdev);
static void check_map(const pid_t pid, const char *filename,
                      struct device_list *dev_head,
                      struct inode_list *ino_head, const uid_t uid,
                      const char access);
static struct stat *get_pidstat(const pid_t pid, const char *filename);
static uid_t getpiduid(const pid_t pid);
static int print_matches(struct names *names_head, const opt_type opts,
                         const int sig_number);
static int kill_matched_proc(struct procs *pptr, const opt_type opts,
                             const int sig_number);

/*int parse_mount(struct names *this_name, struct device_list **dev_list);*/
static void add_device(struct device_list **dev_list,
                       struct names *this_name, dev_t device);
void fill_unix_cache(struct unixsocket_list **unixsocket_head);
void clear_unix_cache(struct unixsocket_list **unixsocket_head);
static void atexit_clear_unix_cache();
static dev_t find_net_dev(void);
static void scan_procs(
    struct names *names_head, struct inode_list *ino_head,
    struct device_list *dev_head,
    struct unixsocket_list *sockets, dev_t netdev);
static void scan_knfsd(
    struct names *names_head, struct inode_list *ino_head,
    struct device_list *dev_head);
static void scan_mounts(
    struct names *names_head,
    struct inode_list *ino_head,
    struct device_list *dev_head);
static void scan_swaps(
    struct names *names_head,
    struct inode_list *ino_head,
    struct device_list *dev_head);
#ifdef DEBUG
static void debug_match_lists(
    struct names *names_head,
    struct inode_list *ino_head,
    struct device_list *dev_head);
#endif

#if defined(WITH_MOUNTINFO_LIST)
static void clear_mntinfo(void) __attribute__ ((__destructor__));
static void init_mntinfo(void) __attribute__ ((__constructor__));
static int mntstat(const char *path, struct stat *buf);
#endif
static stat_t thestat = stat;
static char *expandpath(const char *path);
static struct unixsocket_list *unixsockets = NULL;
static struct names *names_head = NULL, *names_tail = NULL;
static struct ip_connections *tcp_connection_list = NULL;
static struct ip_connections *udp_connection_list = NULL;
#ifdef WITH_IPV6
static struct ip6_connections *tcp6_connection_list = NULL;
static struct ip6_connections *udp6_connection_list = NULL;
#endif
static struct device_list *match_devices = NULL;
static struct inode_list *match_inodes = NULL;

static void usage(
    const char *errormsg)
{
    if (errormsg != NULL)
        fprintf(stderr, "%s\n", errormsg);

    fprintf(stderr,
        _
        ("Usage: fuser [-fIMuvw] [-a|-s] [-4|-6] [-c|-m|-n SPACE]\n"
         "             [-k [-i] [-SIGNAL]] NAME...\n"
         "       fuser -l\n"
         "       fuser -V\n"
         "Show which processes use the named files, sockets, or filesystems.\n\n"
         "  -a,--all              display unused files too\n"
         "  -i,--interactive      ask before killing (ignored without -k)\n"
         "  -I,--inode            use always inodes to compare files\n"
         "  -k,--kill             kill processes accessing the named file\n"
         "  -l,--list-signals     list available signal names\n"
         "  -m,--mount            show all processes using the named filesystems or\n"
         "                        block device\n"
         "  -M,--ismountpoint     fulfill request only if NAME is a mount point\n"
         "  -n,--namespace SPACE  search in this name space (file, udp, or tcp)\n"
         "  -s,--silent           silent operation\n"
         "  -SIGNAL               send this signal instead of SIGKILL\n"
         "  -u,--user             display user IDs\n"
         "  -v,--verbose          verbose output\n"
         "  -w,--writeonly        kill only processes with write access\n"
         "  -V,--version          display version information\n"));
#ifdef WITH_IPV6
    fprintf(stderr, _("  -4,--ipv4             search IPv4 sockets only\n"
                      "  -6,--ipv6             search IPv6 sockets only\n"));
#endif
    fprintf(stderr, _("  -                     reset options\n\n"
                      "  udp/tcp names: [local_port][,[rmt_host][,[rmt_port]]]\n\n"));
    exit(1);
}

void print_version()
{
    fprintf(stderr, _("fuser (PSmisc) %s\n"), VERSION);
    fprintf(stderr,
        _("Copyright (C) 1993-2022 Werner Almesberger and Craig Small\n\n"));
    fprintf(stderr,
        _("PSmisc comes with ABSOLUTELY NO WARRANTY.\n"
          "This is free software, and you are welcome to redistribute it under\n"
          "the terms of the GNU General Public License.\n"
          "For more information about these matters, see the files named COPYING.\n"));
}

static void scan_procs(
    struct names *names_head, struct inode_list *ino_head,
    struct device_list *dev_head, struct unixsocket_list *sockets,
    dev_t netdev)
{
    DIR *topproc_dir;
    struct dirent *topproc_dent;
    struct inode_list *ino_tmp;
    struct device_list *dev_tmp;
    pid_t pid, my_pid;
    uid_t uid;

    if ( (ino_head == NULL) && (dev_head == NULL) )
        return;

    if ((topproc_dir = opendir("/proc")) == NULL) {
        fprintf(stderr, _("Cannot open /proc directory: %s\n"),
            strerror(errno));
        exit(1);
    }
    my_pid = getpid();
    while ((topproc_dent = readdir(topproc_dir)) != NULL) {
        dev_t cwd_dev, exe_dev, root_dev;
        struct stat *cwd_stat = NULL;
        struct stat *exe_stat = NULL;
        struct stat *root_stat = NULL;

        if (topproc_dent->d_name[0] < '0' || topproc_dent->d_name[0] > '9')    /* Not a process */
            continue;
        pid = atoi(topproc_dent->d_name);
        /* Dont print myself */
        if (pid == my_pid)
            continue;
        uid = getpiduid(pid);

        cwd_stat = get_pidstat(pid, "cwd");
        exe_stat = get_pidstat(pid, "exe");
        root_stat = get_pidstat(pid, "root");
        cwd_dev = cwd_stat ? cwd_stat->st_dev : 0;
        exe_dev = exe_stat ? exe_stat->st_dev : 0;
        root_dev = root_stat ? root_stat->st_dev : 0;

        /* Scan the devices */
        for (dev_tmp = dev_head; dev_tmp != NULL;
             dev_tmp = dev_tmp->next) {
            if (exe_dev == dev_tmp->device)
                add_matched_proc(dev_tmp->name, pid, uid,
                         ACCESS_EXE);
            if (root_dev == dev_tmp->device)
                add_matched_proc(dev_tmp->name, pid, uid,
                         ACCESS_ROOT);
            if (cwd_dev == dev_tmp->device)
                add_matched_proc(dev_tmp->name, pid, uid,
                         ACCESS_CWD);
        }
        for (ino_tmp = ino_head; ino_tmp != NULL;
             ino_tmp = ino_tmp->next) {
            if (exe_dev == ino_tmp->device) {
                if (!exe_stat)
                    exe_stat = get_pidstat(pid, "exe");
                if (exe_stat
                    && exe_stat->st_dev == ino_tmp->device
                    && exe_stat->st_ino == ino_tmp->inode)
                    add_matched_proc(ino_tmp->name, pid,
                             uid, ACCESS_EXE);
            }
            if (root_dev == ino_tmp->device) {
                if (!root_stat)
                    root_stat = get_pidstat(pid, "root");
                if (root_stat
                    && root_stat->st_dev == ino_tmp->device
                    && root_stat->st_ino == ino_tmp->inode)
                    add_matched_proc(ino_tmp->name, pid,
                             uid, ACCESS_ROOT);
            }
            if (cwd_dev == ino_tmp->device) {
                if (!cwd_stat)
                    cwd_stat = get_pidstat(pid, "cwd");
                if (cwd_stat
                    && cwd_stat->st_dev == ino_tmp->device
                    && cwd_stat->st_ino == ino_tmp->inode)
                    add_matched_proc(ino_tmp->name, pid,
                             uid, ACCESS_CWD);
            }
        }
        if (root_stat)
            free(root_stat);
        if (cwd_stat)
            free(cwd_stat);
        if (exe_stat)
            free(exe_stat);
#if !defined (__linux__) && !defined (__CYGWIN__)
        check_dir(pid, "lib", dev_head, ino_head, uid, ACCESS_MMAP,
              sockets, netdev);
        check_dir(pid, "mmap", dev_head, ino_head, uid, ACCESS_MMAP,
              sockets, netdev);
#endif
        check_dir(pid, "fd", dev_head, ino_head, uid, ACCESS_FILE,
              sockets, netdev);
        check_map(pid, "maps", dev_head, ino_head, uid, ACCESS_MMAP);

    } /* while topproc_dent */
    closedir(topproc_dir);
}

static void add_inode(
    struct inode_list **ino_list,
    struct names *this_name,
    dev_t device, ino_t inode)
{
    struct inode_list *ino_tmp, *ino_head;

    if ((ino_tmp =
         (struct inode_list *)malloc(sizeof(struct inode_list))) == NULL)
        return;
    ino_head = *ino_list;
    ino_tmp->name = this_name;
    ino_tmp->device = device;
    ino_tmp->inode = inode;
    ino_tmp->next = ino_head;
    *ino_list = ino_tmp;
}

static void add_device(
    struct device_list **dev_list,
    struct names *this_name,
    dev_t device)
{
    struct device_list *dev_tmp, *dev_head;
#ifdef DEBUG
    fprintf(stderr, "add_device(%s %u\n", this_name->filename,
            (unsigned int)device);
#endif /* DEBUG */

    if ((dev_tmp =
         (struct device_list *)malloc(sizeof(struct device_list))) == NULL)
        return;
    dev_head = *dev_list;
    dev_tmp->name = this_name;
    dev_tmp->device = device;
    dev_tmp->next = dev_head;
    *dev_list = dev_tmp;
}

static void add_ip_conn(
    struct ip_connections **ip_list, const char *protocol,
    struct names *this_name, const int lcl_port, const int rmt_port,
    unsigned long rmt_address)
{
    struct ip_connections *ip_tmp, *ip_head;

    if ((ip_tmp =
         (struct ip_connections *)malloc(sizeof(struct ip_connections))) ==
        NULL)
        return;
    ip_head = *ip_list;
    ip_tmp->name = this_name;
    ip_tmp->lcl_port = lcl_port;
    ip_tmp->rmt_port = rmt_port;
    ip_tmp->rmt_address.s_addr = rmt_address;
    ip_tmp->next = ip_head;

    *ip_list = ip_tmp;
}

#ifdef WITH_IPV6
static void add_ip6_conn(
    struct ip6_connections **ip_list,
    const char *protocol,
    struct names *this_name,
    const int lcl_port,
    const int rmt_port,
    struct in6_addr rmt_address)
{
    struct ip6_connections *ip_tmp, *ip_head;

    if ((ip_tmp =
         (struct ip6_connections *)malloc(sizeof(struct ip6_connections)))
        == NULL)
        return;
    ip_head = *ip_list;
    ip_tmp->name = this_name;
    ip_tmp->lcl_port = lcl_port;
    ip_tmp->rmt_port = rmt_port;
    memcpy(&(ip_tmp->rmt_address), &(rmt_address), sizeof(struct in6_addr));
    ip_tmp->next = ip_head;

    *ip_list = ip_tmp;
}
#endif

/* Adds a normal process only */
static void add_matched_proc(
    struct names *name_list,
    const pid_t pid,
    const uid_t uid,
    const char access)
{
    struct procs *pptr, *last_proc;
    char *pathname;
    char cmdname[101], *cptr;
    int cmdlen;
    FILE *fp;

    last_proc = NULL;
    for (pptr = name_list->matched_procs; pptr != NULL; pptr = pptr->next)
    {
        last_proc = pptr;
        if (pptr->pid == pid)
        {
            pptr->access |= access;
            return;
        }
    }
    /* Not found */
    if ((pptr = (struct procs *)malloc(sizeof(struct procs))) == NULL)
    {
        fprintf(stderr,
            _("Cannot allocate memory for matched proc: %s\n"),
            strerror(errno));
        return;
    }
    pptr->pid = pid;
    pptr->uid = uid;
    pptr->access = access;
    pptr->proc_type = PTYPE_NORMAL;
    pptr->next = NULL;
    /* set command name */
    pptr->command = NULL;

    fp = NULL;
    pathname = NULL;
    if ((asprintf(&pathname, "/proc/%d/stat", pid) > 0) &&
        ((fp = fopen(pathname, "r")) != NULL) &&
        (fscanf(fp, "%*d (%100[^)]", cmdname) == 1))
        if ((pptr->command = (char *)malloc(COMM_LEN + 1)) != NULL)
        {
            cmdlen = 0;
            for (cptr = cmdname; cmdlen < COMM_LEN && *cptr;
                 cptr++)
            {
                if (isprint(*cptr))
                    pptr->command[cmdlen++] = *cptr;
                else if (cmdlen < (COMM_LEN - 4))
                    cmdlen +=
                        sprintf(&(pptr->command[cmdlen]),
                                "\\%03o", *cptr);
            }
            pptr->command[cmdlen] = '\0';
        }
    if (last_proc == NULL)
        name_list->matched_procs = pptr;
    else
        last_proc->next = pptr;
    if (pathname)
        free(pathname);
    if (fp)
        fclose(fp);
}

/* Adds a knfsd etc process */
static void add_special_proc(
    struct names *name_list,
    const char ptype,
    const uid_t uid,
    const char *command)
{
    struct procs *pptr;

    for (pptr = name_list->matched_procs; pptr != NULL; pptr = pptr->next)
    {
        if (pptr->proc_type == ptype)
            return;
    }
    if ((pptr = malloc(sizeof(struct procs))) == NULL)
    {
        fprintf(stderr,
            _("Cannot allocate memory for matched proc: %s\n"),
            strerror(errno));
        return;
    }
    pptr->pid = 0;
    pptr->uid = uid;
    pptr->access = 0;
    pptr->proc_type = ptype;
    /* Append the special processes */
    pptr->next = name_list->matched_procs;
    name_list->matched_procs = pptr;
    /* set command name */
    pptr->command = strdup(command);
}

int parse_file(
    struct names *this_name,
    struct inode_list **ino_list,
    const opt_type opts)
{
    char *new = expandpath(this_name->filename);
    if (new)
    {
        if (this_name->filename)
            free(this_name->filename);
        this_name->filename = strdup(new);
    }
    if (timeout(thestat, this_name->filename, &(this_name->st), 5) != 0)
    {
        if (errno == ENOENT)
            fprintf(stderr,
                _("Specified filename %s does not exist.\n"),
                this_name->filename);
        else
            fprintf(stderr, _("Cannot stat %s: %s\n"),
                this_name->filename, strerror(errno));
        return -1;
    }
#ifdef DEBUG
    printf("adding file %s %lX %lX\n", this_name->filename,
           (unsigned long)this_name->st.st_dev,
           (unsigned long)this_name->st.st_ino);
#endif                /* DEBUG */
    add_inode(ino_list, this_name, this_name->st.st_dev,
              this_name->st.st_ino);
    return 0;
}

int parse_unixsockets(
    struct names *this_name,
    struct inode_list **ino_list,
    struct unixsocket_list *sun_head)
{
    struct unixsocket_list *sun_tmp;
    dev_t net_dev;

    net_dev = find_net_dev();

    for (sun_tmp = sun_head; sun_tmp != NULL; sun_tmp = sun_tmp->next)
    {
        if (sun_tmp->dev == this_name->st.st_dev
            && sun_tmp->inode == this_name->st.st_ino)
        {
            add_inode(ino_list, this_name, net_dev,
                      sun_tmp->net_inode);
            return 0;
        }
    }
    return 0;
}

int parse_mounts(
    struct names *this_name,
    struct device_list **dev_list,
    const opt_type opts)
{
    dev_t match_device;

    if (S_ISBLK(this_name->st.st_mode))
        match_device = this_name->st.st_rdev;
    else
        match_device = this_name->st.st_dev;
    add_device(dev_list, this_name, match_device);
    return 0;
}

#ifdef WITH_IPV6
int parse_inet(
    struct names *this_name,
    const int ipv4_only,
    const int ipv6_only,
    struct ip_connections **ip_list,
    struct ip6_connections **ip6_list)
#else
int parse_inet(
    struct names *this_name,
    struct ip_connections **ip_list)
#endif
{
    struct addrinfo *res, *resptr;
    struct addrinfo hints;
    int errcode;
    char *lcl_port_str, *rmt_addr_str, *rmt_port_str, *tmpstr, *tmpstr2;
    in_port_t lcl_port;
    struct sockaddr_in *sin;
#ifdef WITH_IPV6
    struct sockaddr_in6 *sin6;
#endif
    char hostspec[100];
    char *protocol;
    int i;

    if ((protocol = strchr(this_name->filename, '/')) == NULL)
        return -1;
    protocol++;
    if (protocol[0] == '\0')
        return -1;
    for (i = 0;
         i < 99 && this_name->filename[i] != '\0'
         && this_name->filename[i] != '/'; i++)
        hostspec[i] = this_name->filename[i];
    hostspec[i] = '\0';

    lcl_port_str = rmt_addr_str = rmt_port_str = NULL;
    /* Split out the names */
    if ((tmpstr = strchr(hostspec, ',')) == NULL)
    {
        /* Single option */
        lcl_port_str = strdup(hostspec);
    } else {
        if (tmpstr == hostspec)
            lcl_port_str = NULL;
        else {
            *tmpstr = '\0';
            lcl_port_str = strdup(hostspec);
        }
        tmpstr++;
        if (*tmpstr != '\0') {
            if ((tmpstr2 = strchr(tmpstr, ',')) == NULL)
            {
                /* Only 2 options */
                rmt_addr_str = tmpstr;
            } else {
                if (tmpstr2 == tmpstr)
                    rmt_addr_str = NULL;
                else {
                    rmt_addr_str = tmpstr;
                    *tmpstr2 = '\0';
                }
                tmpstr2++;
                if (*tmpstr2 != '\0')
                    rmt_port_str = tmpstr2;
            }
        }
    }
#ifdef DEBUG
    printf("parsed to lp %s rh %s rp %s\n", lcl_port_str, rmt_addr_str,
           rmt_port_str);
#endif

    memset(&hints, 0, sizeof(hints));
#ifdef WITH_IPV6
    if (ipv6_only)
    {
        hints.ai_family = PF_INET6;
    } else if (ipv4_only)
    {
        hints.ai_family = PF_INET;
    } else
        hints.ai_family = PF_UNSPEC;
#else
    hints.ai_family = PF_INET;
#endif
    if (strcmp(protocol, "tcp") == 0)
        hints.ai_socktype = SOCK_STREAM;
    else
        hints.ai_socktype = SOCK_DGRAM;

    if (lcl_port_str == NULL)
    {
        lcl_port = 0;
    } else {
        /* Resolve local port first */
        if ((errcode =
             getaddrinfo(NULL, lcl_port_str, &hints, &res)) != 0)
        {
            fprintf(stderr, _("Cannot resolve local port %s: %s\n"),
                    lcl_port_str, gai_strerror(errcode));
            free(lcl_port_str);
            return -1;
        }
        free(lcl_port_str);
        if (res == NULL)
            return -1;
        switch (res->ai_family)
        {
        case AF_INET:
            lcl_port =
                ((struct sockaddr_in *)(res->ai_addr))->sin_port;
            break;
#ifdef WITH_IPV6
        case AF_INET6:
            lcl_port =
                ((struct sockaddr_in6 *)(res->ai_addr))->sin6_port;
            break;
#endif
        default:
            fprintf(stderr, _("Unknown local port AF %d\n"),
                    res->ai_family);
            freeaddrinfo(res);
            return -1;
        }
        freeaddrinfo(res);
    }
    res = NULL;
    if (rmt_addr_str == NULL && rmt_port_str == NULL)
    {
        add_ip_conn(ip_list, protocol, this_name, ntohs(lcl_port), 0,
                    INADDR_ANY);
#ifdef WITH_IPV6
        add_ip6_conn(ip6_list, protocol, this_name, ntohs(lcl_port), 0,
                     in6addr_any);
#endif
        return 0;
    } else {
        /* Resolve remote address and port */
        if (getaddrinfo(rmt_addr_str, rmt_port_str, &hints, &res) == 0)
        {
            for (resptr = res; resptr != NULL;
                 resptr = resptr->ai_next)
            {
                switch (resptr->ai_family)
                {
                case AF_INET:
                    sin = (struct sockaddr_in *)
                        resptr->ai_addr;
                    if (rmt_addr_str == NULL)
                    {
                        add_ip_conn(ip_list, protocol, this_name,
                                    ntohs(lcl_port), ntohs(sin->sin_port),
                                    INADDR_ANY);
                    } else {
                        add_ip_conn(ip_list, protocol, this_name,
                                    ntohs(lcl_port), ntohs(sin->sin_port),
                                    sin->sin_addr.s_addr);
                    }
                    break;
#ifdef WITH_IPV6
                case AF_INET6:
                    sin6 = (struct sockaddr_in6 *) resptr->ai_addr;
                    if (rmt_addr_str == NULL)
                    {
                        add_ip6_conn(ip6_list, protocol, this_name,
                                     ntohs(lcl_port), ntohs(sin6->sin6_port),
                                     in6addr_any);
                    } else {
                        add_ip6_conn(ip6_list, protocol, this_name,
                                     ntohs(lcl_port), ntohs(sin6->sin6_port),
                                     sin6->sin6_addr);
                    }
                    break;
#endif
                }
            }    /*while */
            freeaddrinfo(res);
            return 0;
        }
    }
    return 1;
}

void find_net_sockets(
    struct inode_list **ino_list,
    struct ip_connections *conn_list,
    const char *protocol,
    dev_t netdev)
{
    FILE *fp;
    char pathname[200], line[BUFSIZ];
    unsigned long loc_port, rmt_port;
    unsigned long rmt_addr;
    unsigned long long scanned_inode;
    ino_t inode;
    struct ip_connections *conn_tmp;

    if (snprintf(pathname, 200, "/proc/net/%s", protocol) < 0)
        return;

    if ((fp = fopen(pathname, "r")) == NULL)
    {
        fprintf(stderr, _("Cannot open protocol file \"%s\": %s\n"),
                pathname, strerror(errno));
        return;
    }
    while (fgets(line, BUFSIZ, fp) != NULL)
    {
        if (sscanf
            (line,
             "%*u: %*x:%lx %08lx:%lx %*x %*x:%*x %*x:%*x %*x %*d %*d %llu",
             &loc_port, &rmt_addr, &rmt_port, &scanned_inode) != 4)
            continue;
#ifdef DEBUG
        printf("Found IPv4 *:%lu with %s:%lu\n", loc_port,
               inet_ntoa(*((struct in_addr *)&rmt_addr)), rmt_port);
#endif                /* DEBUG */
        inode = scanned_inode;
        for (conn_tmp = conn_list; conn_tmp != NULL;
             conn_tmp = conn_tmp->next)
        {
#ifdef DEBUG
            printf("  Comparing with *.%lu %s:%lu\n",
                   conn_tmp->lcl_port,
                   inet_ntoa(conn_tmp->rmt_address),
                   conn_tmp->rmt_port);
#endif
            if ((conn_tmp->lcl_port == 0
                 || conn_tmp->lcl_port == loc_port)
                && (conn_tmp->rmt_port == 0
                || conn_tmp->rmt_port == rmt_port)
                && (conn_tmp->rmt_address.s_addr == INADDR_ANY
                ||
                (memcmp
                 (&(conn_tmp->rmt_address), &(rmt_addr),
                  4) == 0)))
            {
                /* add inode to list */
#ifdef DEBUG
                printf("Added inode!\n");
#endif                /* DEBUG */
                add_inode(ino_list, conn_tmp->name, netdev, inode);
            }
        }

    }
    fclose(fp);
}

#ifdef WITH_IPV6
void find_net6_sockets(
    struct inode_list **ino_list,
    struct ip6_connections *conn_list,
    const char *protocol,
    const dev_t netdev)
{
    FILE *fp;
    char pathname[200], line[BUFSIZ];
    unsigned long loc_port, rmt_port;
    struct in6_addr rmt_addr;
    unsigned int tmp_addr[4];
    char rmt_addr6str[INET6_ADDRSTRLEN];
    struct ip6_connections *conn_tmp;
    unsigned long long scanned_inode;
    ino_t inode;

    if (snprintf(pathname, 200, "/proc/net/%s6", protocol) < 0)
        return;

    if ((fp = fopen(pathname, "r")) == NULL)
    {
#ifdef DEBUG
        printf("Cannot open protocol file \"%s\": %s\n", pathname,
               strerror(errno));
#endif                /* DEBUG */
        return;
    }
    while (fgets(line, BUFSIZ, fp) != NULL)
    {
        if (sscanf
            (line,
             "%*u: %*x:%lx %08x%08x%08x%08x:%lx %*x %*x:%*x %*x:%*x %*x %*d %*d %llu",
             &loc_port, &(tmp_addr[0]), &(tmp_addr[1]), &(tmp_addr[2]),
             &(tmp_addr[3]), &rmt_port, &scanned_inode) != 7)
            continue;
        inode = scanned_inode;
        rmt_addr.s6_addr32[0] = tmp_addr[0];
        rmt_addr.s6_addr32[1] = tmp_addr[1];
        rmt_addr.s6_addr32[2] = tmp_addr[2];
        rmt_addr.s6_addr32[3] = tmp_addr[3];
        inet_ntop(AF_INET6, &rmt_addr, rmt_addr6str, INET6_ADDRSTRLEN);
#ifdef DEBUG
        printf("Found IPv6 %ld with %s:%ld\n", loc_port, rmt_addr6str,
               rmt_port);
#endif                /* DEBUG */
        for (conn_tmp = conn_list; conn_tmp != NULL;
             conn_tmp = conn_tmp->next)
        {
            inet_ntop(AF_INET6, &conn_tmp->rmt_address,
                      rmt_addr6str, INET6_ADDRSTRLEN);
#ifdef DEBUG
            printf("  Comparing with *.%lu %s:%lu ...\n",
                   conn_tmp->lcl_port, rmt_addr6str,
                   conn_tmp->rmt_port);
#endif                /* DEBUG */
            if ((conn_tmp->lcl_port == 0
                 || conn_tmp->lcl_port == loc_port)
                && (conn_tmp->rmt_port == 0
                || conn_tmp->rmt_port == rmt_port)
                &&
                (memcmp(&(conn_tmp->rmt_address), &in6addr_any, 16)
                 == 0
                 ||
                 (memcmp(&(conn_tmp->rmt_address), &(rmt_addr), 16)
                  == 0)))
            {
                add_inode(ino_list, conn_tmp->name, netdev, inode);
            }
        }
    }
    fclose(fp);
}
#endif

static void read_proc_mounts(
    struct mount_list **mnt_list)
{
    FILE *fp;
    char line[BUFSIZ];
    char *find_mountp;
    char *find_space;
    struct mount_list *mnt_tmp;

    if ((fp = fopen(PROC_MOUNTS, "r")) == NULL)
    {
        fprintf(stderr, "Cannot open %s\n", PROC_MOUNTS);
        return;
    }
    while (fgets(line, BUFSIZ, fp) != NULL)
    {
        if ((find_mountp = strchr(line, ' ')) == NULL)
            continue;
        find_mountp++;
        if ((find_space = strchr(find_mountp, ' ')) == NULL)
            continue;
        *find_space = '\0';
        if ((mnt_tmp = malloc(sizeof(struct mount_list))) == NULL)
            continue;
        if ((mnt_tmp->mountpoint = strdup(find_mountp)) == NULL)
        {
            free(mnt_tmp);
            continue;
        }
        mnt_tmp->next = *mnt_list;
        *mnt_list = mnt_tmp;
    }
    fclose(fp);
}

static void free_proc_mounts(
    struct mount_list *mnt_list)
{
    struct mount_list *mnt_tmp, *mnt_next;

    mnt_tmp = mnt_list;
    while (mnt_tmp != NULL)
    {
        mnt_next = mnt_tmp->next;
        free(mnt_tmp->mountpoint);
        free(mnt_tmp);
        mnt_tmp = mnt_next;
    }
}

/*
 * Free up structures allocated in add_matched_proc and add_special_proc
 */
static void free_matched_procs(
    struct procs *matched_procs)
{
    struct procs *procs_tmp, *procs_next;

    procs_tmp = matched_procs;
    while (procs_tmp != NULL)
    {
        procs_next = procs_tmp->next;
        if (procs_tmp->command)
            free(procs_tmp->command);
        free(procs_tmp);
        procs_tmp = procs_next;
    }
}

static void free_names()
{
    while(names_head != NULL)
    {
        struct names *this_name = names_head;
        names_head = this_name->next;
        free_matched_procs(this_name->matched_procs);
        free(this_name->filename);
        free(this_name);
    }
    names_tail = NULL;
}

/*
 * Free up structures allocated in add_ip_conn and add_ip6_conn
 */
static void free_connection(
    struct ip_connections **conn)
{
    struct ip_connections *conn_tmp, *conn_next;

    conn_tmp = *conn;
    while(conn_tmp != NULL)
    {
        conn_next = conn_tmp->next;
        free(conn_tmp);
        conn_tmp = conn_next;
    }
    *conn = NULL;
}

#ifdef WITH_IPV6
static void free_connection6(
    struct ip6_connections **conn)
{
    struct ip6_connections *conn_tmp, *conn_next;

    conn_tmp = *conn;
    while(conn_tmp != NULL)
    {
        conn_next = conn_tmp->next;
        free(conn_tmp);
        conn_tmp = conn_next;
    }
    *conn = NULL;
}
#endif

/*
 * Free up structures allocated in add_inode
 */
static void free_inodes(
    struct inode_list **match_inodes)
{
    struct inode_list *inode_tmp, *inode_next;

    inode_tmp = *match_inodes;
    while(inode_tmp != NULL)
    {
        inode_next = inode_tmp->next;
        free(inode_tmp);
        inode_tmp = inode_next;
    }
    *match_inodes = NULL;
}

/*
 * Free up structures allocated in add_device
 */
static void free_devices(
    struct device_list **match_devices)
{
    struct device_list *device_tmp, *device_next;

    device_tmp = *match_devices;
    while(device_tmp != NULL)
    {
        device_next = device_tmp->next;
        free(device_tmp);
        device_tmp = device_next;
    }
    *match_devices = NULL;
}

static void atexit_free_lists()
{
    free_connection(&tcp_connection_list);
    free_connection(&udp_connection_list);
#ifdef WITH_IPV6
    free_connection6(&tcp6_connection_list);
    free_connection6(&udp6_connection_list);
#endif

    free_inodes(&match_inodes);
    free_devices(&match_devices);

    free_names();
}


static int is_mountpoint(
    struct mount_list **mnt_list,
    char *arg)
{
    char *p;
    struct mount_list *mnt_tmp;

    if (*arg == '\0')
        return 0;
    /* Remove trailing slashes. */
    for (p = arg; *p != '\0'; p++)
        /*do nothing*/;
    while (*(--p) == '/' && p > arg)
        *p = '\0';

    for (mnt_tmp = *mnt_list; mnt_tmp != NULL; mnt_tmp = mnt_tmp->next)
        if (!strcmp(mnt_tmp->mountpoint, arg))
            return 1;
    return 0;
}

static void check_mountpoints(
    struct mount_list **mnt_list,
    struct names **head,
    struct names **tail)
{
    struct names *this, *last;

    last = NULL;
    for(this = *head; this != NULL; this = this->next)
    {
        if (this->name_space == NAMESPACE_FILE &&
            !is_mountpoint(mnt_list, this->filename))
        {
            fprintf(stderr,
                    _("Specified filename %s is not a mountpoint.\n"),
                    this->filename);
            /* Remove from list */
            if (last)
                last->next = this->next;
            if (*head == this)
                *head = this->next;
            if (*tail == this)
                *tail = last;
        } else {
            last = this;
        }
    }
}

int main(int argc, char *argv[])
{
    opt_type opts;
    int sig_number;
#ifdef WITH_IPV6
    int ipv4_only, ipv6_only;
#endif
    unsigned char default_namespace = NAMESPACE_FILE;

    dev_t netdev;
    struct names *this_name;
    int argc_cnt;
    char *current_argv, *option;
    char option_buf[3];
    struct option *optr;
    char *nsptr;
    int skip_argv;

    struct option options[] = {
        {"all", 0, NULL, 'a'},
        {"kill", 0, NULL, 'k'},
        {"interactive", 0, NULL, 'i'},
        {"inode", 0, NULL, 'I'},
        {"list-signals", 0, NULL, 'l'},
        {"mount", 0, NULL, 'm'},
        {"ismountpoint", 0, NULL, 'M'},
        {"namespace", 1, NULL, 'n'},
        {"silent", 0, NULL, 's'},
        {"user", 0, NULL, 'u'},
        {"verbose", 0, NULL, 'v'},
        {"writeonly", 0, NULL, 'w'},
        {"version", 0, NULL, 'V'},
#ifdef WITH_IPV6
        {"ipv4", 0, NULL, '4'},
        {"ipv6", 0, NULL, '6'},
#endif
        {0, 0, 0, 0}
    };

#ifdef WITH_IPV6
    ipv4_only = ipv6_only = 0;
#endif
    this_name = NULL;
    opts = 0;
    sig_number = SIGKILL;

#ifdef ENABLE_NLS
    /* Set up the i18n */
    setlocale(LC_ALL, "");
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
#endif

    netdev = find_net_dev();
#ifndef __CYGWIN__        /* Cygwin doesn't support /proc/net/unix */
    fill_unix_cache(&unixsockets);
    atexit(atexit_clear_unix_cache);
#endif
    atexit(atexit_free_lists);

    for (argc_cnt = 1; argc_cnt < argc; argc_cnt++)
    {
        current_argv = argv[argc_cnt];
        if (current_argv[0] == '-') /* its an option */
        {
            if (current_argv[1] == '-') /* its a long option */
            {
                if (current_argv[2] == '\0')    /* -- */
                    break;
                /* Parse the long options */
                option = option_buf;
                for (optr = options; optr->name != NULL; optr++)
                {
                    if (strcmp(current_argv + 2, optr->name) == 0)
                    {
                        sprintf(option_buf, "-%c", (char)optr->val);
                        break;
                    }
                }
                if (optr->name == NULL)
                {
                    fprintf(stderr, _("%s: Invalid option %s\n"),
                            argv[0], current_argv);
                    usage(NULL);
                }
            } else {
                option = current_argv;
            }
            skip_argv = 0;
            while (*(++option) != '\0' && !skip_argv)/* skips over the - */
            {
                switch (*option)
                {
#ifdef WITH_IPV6
                case '4':
                    ipv4_only = 1;
                    break;
                case '6':
                    ipv6_only = 1;
                    break;
#endif                /* WITH_IPV6 */
                case 'a':
                    opts |= OPT_ALLFILES;
                    break;
                case 'c':
                    opts |= OPT_MOUNTS;
                    break;
                case 'f':
                    /* ignored */
                    break;
                case 'h':
                    usage(NULL);
                    break;
                case 'i':
                    opts |= OPT_INTERACTIVE;
                    break;
                case 'I':
#if defined(WITH_MOUNTINFO_LIST)
                    opts |= OPT_ALWAYSSTAT;
#endif
                    break;
                case 'k':
                    opts |= OPT_KILL;
                    break;
                case 'l':
                    list_signals();
                    return 0;
                case 'm':
                    opts |= OPT_MOUNTS;
                    break;
                case 'M':
                    opts |= OPT_ISMOUNTPOINT;
                    break;
                case 'n':
                    argc_cnt++;
                    if (argc_cnt >= argc)
                    {
                        usage(_
                              ("Namespace option requires an argument."));
                    }
                    skip_argv = 1;
                    //while(option != '\0') option++;
                    if (strcmp(argv[argc_cnt], "tcp") == 0)
                        default_namespace = NAMESPACE_TCP;
                    else if (strcmp(argv[argc_cnt], "udp") == 0)
                        default_namespace = NAMESPACE_UDP;
                    else if (strcmp(argv[argc_cnt], "file") == 0)
                        default_namespace = NAMESPACE_FILE;
                    else
                        usage(_
                              ("Invalid namespace name"));
                    break;
                case 's':
                    opts |= OPT_SILENT;
                    break;
                case 'u':
                    opts |= OPT_USER;
                    break;
                case 'v':
                    opts |= OPT_VERBOSE;
                    break;
                case 'w':
                    opts |= OPT_WRITE;
                    break;
                case 'V':
                    print_version();
                    return 0;
                default:
                    if (isupper(*option) || isdigit(*option))
                    {
                        sig_number = get_signal(current_argv + 1, argv[0]);
                        skip_argv = 1;
                        break;
                    }
                    fprintf(stderr, "%s: Invalid option %c\n",
                            argv[0], *option);
                    usage(NULL);
                    break;
                }    /* switch */
            }    /* while option */
            continue;
        }

#if defined(WITH_MOUNTINFO_LIST)
        if ((opts & OPT_ALWAYSSTAT) == 0)
            thestat = mntstat;
#endif
        /* an option */
        /* Not an option, must be a file specification */
        if ((this_name = malloc(sizeof(struct names))) == NULL)
            continue;
        this_name->next = NULL;
        /* try to find namespace spec */
        this_name->name_space = default_namespace;
        if (((nsptr = strchr(current_argv, '/')) != NULL)
            && (nsptr != current_argv))
        {
            if (strcmp(nsptr + 1, "tcp") == 0)
            {
                this_name->name_space = NAMESPACE_TCP;
                *nsptr = '\0';
            } else if (strcmp(nsptr + 1, "udp") == 0)
            {
                this_name->name_space = NAMESPACE_UDP;
                *nsptr = '\0';
            } else if (strcmp(nsptr + 1, "file") == 0)
            {
                this_name->name_space = NAMESPACE_FILE;
                *nsptr = '\0';
            }
        }
        this_name->matched_procs = NULL;
        if (opts & (OPT_MOUNTS | OPT_ISMOUNTPOINT)
            && this_name->name_space != NAMESPACE_FILE)
        {
            free(this_name);
            usage(_
                  ("You can only use files with mountpoint options"));
        }
        switch (this_name->name_space)
        {
        case NAMESPACE_TCP:
            if (asprintf
                (&(this_name->filename), "%s/tcp",
                 current_argv) > 0)
            {
#ifdef WITH_IPV6
                parse_inet(this_name, ipv4_only, ipv6_only,
                           &tcp_connection_list, &tcp6_connection_list);
#else
                parse_inet(this_name, &tcp_connection_list);
#endif
            }
            break;
        case NAMESPACE_UDP:
            if (asprintf (&(this_name->filename), "%s/udp", current_argv) > 0)
            {
#ifdef WITH_IPV6
                parse_inet(this_name, ipv4_only, ipv6_only,
                       &udp_connection_list, &udp6_connection_list);
#else
                parse_inet(this_name, &udp_connection_list);
#endif
            }
            break;
        default:    /* FILE */
            this_name->filename = strdup(current_argv);
            if (parse_file(this_name, &match_inodes, opts) == 0)
            {
                if (opts & OPT_MOUNTS)
                    parse_mounts(this_name, &match_devices, opts);
                else
                    parse_unixsockets(this_name, &match_inodes, unixsockets);
            }
            break;
        }

        if (names_head == NULL)
            names_head = this_name;
        if (names_tail != NULL)
            names_tail->next = this_name;
        names_tail = this_name;
    }            /* for across the argvs */
    if (names_head == NULL)
        usage(_("No process specification given"));

    /* Check if -M flag was used and if so check mounts */
    if (opts & OPT_ISMOUNTPOINT)
    {
        struct mount_list *mounts = NULL;
        read_proc_mounts(&mounts);
        check_mountpoints(&mounts, &names_head, &names_tail);
        free_proc_mounts(mounts);
    }

    if (opts & OPT_SILENT)
    {
        opts &= ~OPT_VERBOSE;
        opts &= ~OPT_USER;
        if (opts & OPT_ALLFILES)
            usage(_
                  ("all option cannot be used with silent option."));
    }
#ifdef WITH_IPV6
    if (ipv4_only && ipv6_only)
        usage(_
              ("You cannot search for only IPv4 and only IPv6 sockets at the same time"));
    if (!ipv6_only)
    {
#endif
        if (tcp_connection_list != NULL)
            find_net_sockets(&match_inodes, tcp_connection_list, "tcp", netdev);
        if (udp_connection_list != NULL)
            find_net_sockets(&match_inodes, udp_connection_list, "udp", netdev);
#ifdef WITH_IPV6
    }
    if (!ipv4_only)
    {
        if (tcp6_connection_list != NULL)
            find_net6_sockets(&match_inodes, tcp6_connection_list,
                              "tcp", netdev);
        if (udp6_connection_list != NULL)
            find_net6_sockets(&match_inodes, udp6_connection_list,
                              "udp", netdev);
    }
#endif
    free_connection(&tcp_connection_list);
    free_connection(&udp_connection_list);
#ifdef WITH_IPV6
    free_connection6(&tcp6_connection_list);
    free_connection6(&udp6_connection_list);
#endif
#ifdef DEBUG
    debug_match_lists(names_head, match_inodes, match_devices);
#endif
    scan_procs(names_head, match_inodes, match_devices, unixsockets, netdev);
#ifndef __CYGWIN__        /* Cygwin doesn't support /proc/net/unix */
    clear_unix_cache(&unixsockets);
#endif
    scan_knfsd(names_head, match_inodes, match_devices);
    scan_mounts(names_head, match_inodes, match_devices);
    scan_swaps(names_head, match_inodes, match_devices);
    free_inodes(&match_inodes);
    free_devices(&match_devices);
    return print_matches(names_head, opts, sig_number);
}

/* 
 * returns 0 if match, 1 if no match
 */
static int print_matches(
    struct names *names_head,
    const opt_type opts,
    const int sig_number)
{
    struct names *nptr;
    struct procs *pptr;
    char head = 0;
    char first = 1;
    int len = 0;
    struct passwd *pwent = NULL;
    int have_match = 0;
    int have_kill = 0;
    int name_has_procs = 0;

    for (nptr = names_head; nptr != NULL; nptr = nptr->next)
    {
        if (opts & OPT_SILENT)
        {
            for (pptr = nptr->matched_procs; pptr != NULL;
                 pptr = pptr->next)
            {
                if (pptr->proc_type != PTYPE_NORMAL)
                    continue;

                have_match = 1;
            }
        } else {    /* We're not silent */
            if ((opts & OPT_ALLFILES) == 0)
            {
                name_has_procs = 0;
                if (opts & OPT_VERBOSE)
                {
                    if (nptr->matched_procs)
                        name_has_procs = 1;
                } else {
                    for (pptr = nptr->matched_procs;
                         pptr != NULL; pptr = pptr->next)
                    {
                        if (pptr->proc_type == PTYPE_NORMAL)
                        {
                            name_has_procs = 1;
                            break;
                        }
                    }
                }
            }
            if (name_has_procs == 1 || opts & OPT_ALLFILES)
            {
                if (head == 0 && opts & OPT_VERBOSE)
                {
                    fprintf(stderr,
                        _("%*s USER        PID ACCESS COMMAND\n"), NAME_FIELD, "");
                    head = 1;
                }

                fprintf(stderr, "%s:", nptr->filename);
                len = strlen(nptr->filename) + 1;
            }

            first = 1;
            for (pptr = nptr->matched_procs; pptr != NULL; pptr = pptr->next)
            {
                /* Suppress any special "processes" */
                if (!(opts & OPT_VERBOSE)
                    && (pptr->proc_type != PTYPE_NORMAL))
                    continue;

                have_match = 1;
                if (opts & (OPT_VERBOSE | OPT_USER))
                {
                    if (pwent == NULL || pwent->pw_uid != pptr->uid)
                        pwent = getpwuid(pptr->uid);
                }
                if (len > NAME_FIELD && (opts & OPT_VERBOSE))
                {
                    putc('\n', stderr);
                    len = 0;
                }
                if ((opts & OPT_VERBOSE) || first)
                    while (len++ < NAME_FIELD)
                        putc(' ', stderr);
                if (opts & OPT_VERBOSE)
                {
                    if (pwent == NULL)
                        fprintf(stderr, " %-8s ",
                            _("(unknown)"));
                    else
                        fprintf(stderr, " %-8s ",
                            pwent->pw_name);
                }
                if (pptr->proc_type == PTYPE_NORMAL)
                    printf(" %5d", pptr->pid);
                else
                    printf("kernel");
                fflush(stdout);
                if (opts & OPT_VERBOSE)
                {
                    switch (pptr->proc_type)
                    {
                    case PTYPE_KNFSD:
                        fprintf(stderr, " knfsd ");
                        break;
                    case PTYPE_MOUNT:
                        fprintf(stderr, " mount ");
                        break;
                    case PTYPE_SWAP:
                        fprintf(stderr, " swap  ");
                        break;
                    default:
                        fprintf(stderr, " %c%c%c%c%c ",
                            pptr->access & ACCESS_FILE ?
                            (pptr->access & ACCESS_FILEWR ? 'F' : 'f') : '.',
                            pptr->access & ACCESS_ROOT ?  'r' : '.',
                            pptr->access & ACCESS_CWD ?  'c' : '.',
                            pptr->access & ACCESS_EXE ?  'e' : '.',
                            (pptr->access & ACCESS_MMAP)
                            && !(pptr-> access & ACCESS_EXE) ? 'm' : '.');
                    }    /* switch */
                } else {
                    if (pptr->access & ACCESS_ROOT)
                        putc('r', stderr);
                    if (pptr->access & ACCESS_CWD)
                        putc('c', stderr);
                    if (pptr->access & ACCESS_EXE)
                        putc('e', stderr);
                    else if (pptr->access & ACCESS_MMAP)
                        putc('m', stderr);
                }
                if (opts & OPT_USER)
                {
                    if (pwent == NULL)
                        fprintf(stderr, " %-8s ", _("(unknown)"));
                    else
                        fprintf(stderr, "(%s)", pwent->pw_name);
                }
                if (opts & OPT_VERBOSE)
                {
                    if (pptr->command == NULL)
                        fprintf(stderr, "???\n");
                    else
                        fprintf(stderr, "%s\n", pptr->command);
                }
                len = 0;
                first = 0;
            }
            if (opts & OPT_VERBOSE)
            {
                /* put a newline if showing all files and no procs */
                if (nptr->matched_procs == NULL && (opts & OPT_ALLFILES))
                    putc('\n', stderr);
            } else {
                if (name_has_procs || (opts & OPT_ALLFILES))
                    putc('\n', stderr);
            }
        }        /* be silent */
        if (opts & OPT_KILL)
            have_kill |= kill_matched_proc(nptr->matched_procs,
                                           opts, sig_number);

    }            /* next name */
    if (opts & OPT_KILL)
        return (have_kill == 1 ? 0 : 1);
    else
        return (have_match == 1 ? 0 : 1);

}

static struct stat *get_pidstat(
    const pid_t pid,
    const char *filename)
{
    char pathname[PATH_MAX];
    struct stat *st;

    if ((st = (struct stat *)malloc(sizeof(struct stat))) == NULL)
        return NULL;
    snprintf(pathname, PATH_MAX-1, "/proc/%d/%s", pid, filename);
    if (timeout(thestat, pathname, st, 5) != 0)
    {
        free(st);
        return NULL;
    }
    return st;
}

static void check_dir(
    const pid_t pid,
    const char *dirname,
    struct device_list *dev_head,
    struct inode_list *ino_head,
    const uid_t uid,
    const char access,
    struct unixsocket_list *sockets,
    dev_t netdev)
{
    DIR *dirp;
    dev_t thedev;
    struct dirent *direntry;
    struct inode_list *ino_tmp;
    struct device_list *dev_tmp;
    struct unixsocket_list *sock_tmp;
    struct stat st, lst;
    char *dirpath;
    char filepath[PATH_MAX];
    char real_filepath[PATH_MAX];

    if (asprintf(&dirpath, "/proc/%d/%s", pid, dirname) < 0)
        return;
    if ((dirp = opendir(dirpath)) == NULL)
    {
        free(dirpath);
        return;
    }
    free(dirpath);

    while ((direntry = readdir(dirp)) != NULL)
    {
        if (direntry->d_name[0] < '0' || direntry->d_name[0] > '9')
            continue;

        snprintf(filepath, sizeof filepath - 1, "/proc/%d/%s/%s",
             pid, dirname, direntry->d_name);

        if (timeout(thestat, filepath, &st, 5) != 0)
        {
            if (errno != ENOENT && errno != ENOTDIR)
            {
                fprintf(stderr, _("Cannot stat file %s: %s\n"),
                        filepath, strerror(errno));
            }
        } else {
            thedev = st.st_dev;
            if (thedev == netdev)
            {
                for (sock_tmp = sockets; sock_tmp != NULL;
                     sock_tmp = sock_tmp->next)
                {
                    if (sock_tmp->net_inode == st.st_ino)
                    {
                        st.st_ino = sock_tmp->inode;
                        st.st_dev = sock_tmp->dev;
                        thedev = sock_tmp->dev;
                        break;
                    }
                }
            }
            for (dev_tmp = dev_head; dev_tmp != NULL;
                 dev_tmp = dev_tmp->next)
            {
                if (thedev != dev_tmp->device)
                    continue;

                /* check the paths match if it is not a block device or socket */
                if (! S_ISBLK(dev_tmp->name->st.st_mode)
                    && !S_ISSOCK(st.st_mode))
                {
                    if (readlink(filepath, real_filepath, PATH_MAX-1) < 0)
                    {
                        if (strncmp(dev_tmp->name->filename, filepath,
                                    strlen(dev_tmp->name->filename)) != 0)
                            continue;
                    } else {
                        if (strncmp(dev_tmp->name->filename, real_filepath,
                                    strlen(dev_tmp->name->filename)) != 0)
                            continue;
                    }
                }
                if (access == ACCESS_FILE
                    && (lstat(filepath, &lst) == 0)
                    && (lst.st_mode & S_IWUSR))
                {
                    add_matched_proc(dev_tmp->name, pid, uid,
                                     ACCESS_FILEWR | access);
                } else {
                    add_matched_proc(dev_tmp->name, pid, uid, access);
                }
            }
            for (ino_tmp = ino_head; ino_tmp != NULL; ino_tmp = ino_tmp->next)
            {
                if (thedev != ino_tmp->device)
                    continue;
                if (!st.st_ino && timeout(thestat, filepath, &st, 5) != 0)
                {
                    fprintf(stderr, _("Cannot stat file %s: %s\n"),
                            filepath, strerror(errno));
                    continue;
                }
                if (st.st_ino == ino_tmp->inode)
                {
                    if (access == ACCESS_FILE
                        && (lstat(filepath, &lst) == 0)
                        && (lst.st_mode & S_IWUSR))
                    {
                        add_matched_proc(ino_tmp->name, pid, uid,
                                         ACCESS_FILEWR | access);
                    } else {
                        add_matched_proc(ino_tmp->name, pid, uid, access);
                    }
                }
            }
        }
    }            /* while fd_dent */
    closedir(dirp);
}

static void check_map(
    const pid_t pid,
    const char *filename,
   struct device_list *dev_head,
   struct inode_list *ino_head,
   const uid_t uid,
   const char access)
{
    char *pathname;
    char line[BUFSIZ];
    struct inode_list *ino_tmp;
    struct device_list *dev_tmp;
    FILE *fp;
    unsigned long long tmp_inode;
    unsigned int tmp_maj, tmp_min;
    dev_t tmp_device;

    if (asprintf(&pathname, "/proc/%d/%s", pid, filename) < 0)
        return;
    if ((fp = fopen(pathname, "r")) == NULL)
    {
        free(pathname);
        return;
    }
    free(pathname);
    while (fgets(line, BUFSIZ, fp))
    {
        if (sscanf(line, "%*s %*s %*s %x:%x %lld",
                   &tmp_maj, &tmp_min, &tmp_inode) == 3)
        {
            tmp_device = tmp_maj * 256 + tmp_min;
            for (dev_tmp = dev_head; dev_tmp != NULL; dev_tmp = dev_tmp->next)
                if (dev_tmp->device == tmp_device)
                    add_matched_proc(dev_tmp->name, pid, uid, access);
            for (ino_tmp = ino_head; ino_tmp != NULL; ino_tmp = ino_tmp->next)
                if (ino_tmp->device == tmp_device
                    && ino_tmp->inode == tmp_inode)
                    add_matched_proc(ino_tmp->name, pid, uid, access);
        }
    }
    fclose(fp);
}

static uid_t getpiduid(
    const pid_t pid)
{
    char *pathname;
    struct stat st;

    if (asprintf(&pathname, "/proc/%d", pid) < 0)
        return 0;
    if (timeout(thestat, pathname, &st, 5) != 0)
    {
        free(pathname);
        return 0;
    }
    free(pathname);
    return st.st_uid;
}

/*
 * fill_unix_cache : Create a list of Unix sockets
 *   This list is used later for matching purposes
 */
void fill_unix_cache(
    struct unixsocket_list **unixsocket_head)
{
    FILE *fp;
    char line[BUFSIZ];
    unsigned long long scanned_inode;
    struct stat st;
    struct unixsocket_list *newsocket;

    if ((fp = fopen("/proc/net/unix", "r")) == NULL)
    {
        fprintf(stderr, _("Cannot open /proc/net/unix: %s\n"),
                strerror(errno));
        return;
    }
    while (fgets(line, BUFSIZ, fp) != NULL)
    {
        char *path;
        char *scanned_path = NULL;
        if (sscanf(line, "%*x: %*x %*x %*x %*x %*d %llu %ms",
               &scanned_inode, &scanned_path) != 2)
        {
            if (scanned_path)
                free(scanned_path);
            continue;
        }
        if (scanned_path == NULL)
            continue;
        path = scanned_path;
        if (*scanned_path == '@')
            scanned_path++;
        if (timeout(thestat, scanned_path, &st, 5) < 0)
        {
            free(path);
            continue;
        }
        if ((newsocket = (struct unixsocket_list *)
             malloc(sizeof(struct unixsocket_list))) == NULL)
        {
            free(path);
            continue;
        }
        newsocket->sun_name = strdup(scanned_path);
        newsocket->inode = st.st_ino;
        newsocket->dev = st.st_dev;
        newsocket->net_inode = scanned_inode;
        newsocket->next = *unixsocket_head;
        *unixsocket_head = newsocket;
        free(path);
    } /* while */

    fclose(fp);
}

/*
 * Free up the list of Unix sockets
 */
void clear_unix_cache(
    struct unixsocket_list **unixsocket_head)
{
    while(*unixsocket_head != NULL)
    {
        struct unixsocket_list *oldsocket = *unixsocket_head;
        *unixsocket_head = oldsocket->next;
        free(oldsocket->sun_name);
        free(oldsocket);
    }
}

static void atexit_clear_unix_cache()
{
    clear_unix_cache(&unixsockets);
}

#ifdef DEBUG
/* often not used, doesn't need translation */
static void debug_match_lists(
    struct names *names_head,
    struct inode_list *ino_head,
    struct device_list *dev_head)
{
    struct names *nptr;
    struct inode_list *iptr;
    struct device_list *dptr;

    fprintf(stderr, "Specified Names:\n");
    for (nptr = names_head; nptr != NULL; nptr = nptr->next)
    {
        fprintf(stderr, "\t%s %c\n", nptr->filename, nptr->name_space);
    }
    fprintf(stderr, "\nInodes:\n");
    for (iptr = ino_head; iptr != NULL; iptr = iptr->next)
    {
        fprintf(stderr, "  Dev:%0lx Inode:(%0ld) 0x%0lx => %s\n",
                (unsigned long)iptr->device, (unsigned long)iptr->inode,
                (unsigned long)iptr->inode, iptr->name->filename);
    }
    fprintf(stderr, "\nDevices:\n");
    for (dptr = dev_head; dptr != NULL; dptr = dptr->next)
    {
        fprintf(stderr, "\tDev:%0lx\n", (unsigned long)dptr->device);
    }
}

#endif

/* 0 = no, 1=yes */
static int ask(
    const pid_t pid)
{
    int res;
    size_t len = 0;
    char *line = NULL;

    fflush(stdout);
    while (1)
    {
        fprintf(stderr, _("Kill process %d ? (y/N) "), pid);
        fflush(stderr);
        if (getline(&line, &len, stdin) < 0)
            return 0;
        if (line[0] == '\n')
        {
            free(line);
            return 0;
        }
        res = rpmatch(line);
        if (res >= 0)
        {
            free(line);
            return res;
        }
    }            /* while */
}

static int kill_matched_proc(
    struct procs *proc_head,
    const opt_type opts,
    const int sig_number)
{
    struct procs *pptr;
    pid_t mypid;
    int ret = 0;

    mypid = getpid();

    for (pptr = proc_head; pptr != NULL; pptr = pptr->next)
    {
        if (pptr->pid == mypid)
            continue;    /* dont kill myself */
        if (pptr->proc_type != PTYPE_NORMAL)
            continue;
        if ((opts & OPT_WRITE) && ((pptr->access & ACCESS_FILEWR) == 0))
            continue;
        if ((opts & OPT_INTERACTIVE) && (ask(pptr->pid) == 0))
            continue;
        if (kill(pptr->pid, sig_number) < 0)
        {
            fprintf(stderr, _("Could not kill process %d: %s\n"),
                    pptr->pid, strerror(errno));
            continue;
        }
        ret = 1;
    }
    return ret;
}

static dev_t find_net_dev(void)
{
    int skt;
    struct stat st;

    if ((skt = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
    {
        fprintf(stderr, _("Cannot open a network socket.\n"));
        return -1;
    }
    if (fstat(skt, &st) != 0)
    {
        fprintf(stderr, _("Cannot find socket's device number.\n"));
        close(skt);
        return -1;
    }
    close(skt);
    return st.st_dev;
}

static void scan_knfsd(
    struct names *names_head,
    struct inode_list *ino_head,
    struct device_list *dev_head)
{
    struct device_list *dev_tmp;
    struct inode_list *ino_tmp;
    FILE *fp;
    char line[BUFSIZ];
    char *find_space;
    struct stat st;

    if ( (ino_head == NULL) && (dev_head == NULL) )
        return;


    if ((fp = fopen(KNFSD_EXPORTS, "r")) == NULL)
    {
#ifdef DEBUG
        printf("Cannot open %s\n", KNFSD_EXPORTS);
#endif
        return;
    }
    while (fgets(line, BUFSIZ, fp) != NULL)
    {
        if (line[0] == '#')
            continue;
        
        if ((find_space = strpbrk(line, " \t")) == NULL)
            continue;

        *find_space = '\0';
        if (timeout(thestat, line, &st, 5) != 0)
            continue;

        /* Scan the devices */
        for (dev_tmp = dev_head; dev_tmp != NULL;
             dev_tmp = dev_tmp->next)
        {
            if (st.st_dev == dev_tmp->device)
                add_special_proc(dev_tmp->name, PTYPE_KNFSD, 0, line);
        }

        for (ino_tmp = ino_head; ino_tmp != NULL;
             ino_tmp = ino_tmp->next)
        {
            if (st.st_dev == ino_tmp->device
                && st.st_ino == ino_tmp->inode)
                add_special_proc(ino_tmp->name, PTYPE_KNFSD, 0, line);
        }
    }
    fclose(fp);
}

static void scan_mounts(
    struct names *names_head,
    struct inode_list *ino_head,
    struct device_list *dev_head)
{
    struct device_list *dev_tmp;
    struct inode_list *ino_tmp;
    FILE *fp;
    char line[BUFSIZ];
    char *find_mountp;
    char *find_space;
    struct stat st;

    if ( (ino_head == NULL) && (dev_head == NULL) )
        return;


    if ((fp = fopen(PROC_MOUNTS, "r")) == NULL)
    {
        fprintf(stderr, "Cannot open %s\n", PROC_MOUNTS);
        return;
    }
    while (fgets(line, BUFSIZ, fp) != NULL)
    {
        if ((find_mountp = strchr(line, ' ')) == NULL)
            continue;
        find_mountp++;
        if ((find_space = strchr(find_mountp, ' ')) == NULL)
            continue;
        *find_space = '\0';

        if (timeout(thestat, find_mountp, &st, 5) != 0)
            continue;
        /* Scan the devices */
        for (dev_tmp = dev_head; dev_tmp != NULL;
             dev_tmp = dev_tmp->next)
        {
            if (st.st_dev == dev_tmp->device)
                add_special_proc(dev_tmp->name, PTYPE_MOUNT, 0, find_mountp);
        }
        for (ino_tmp = ino_head; ino_tmp != NULL;
             ino_tmp = ino_tmp->next)
        {
            if (st.st_dev == ino_tmp->device
                && st.st_ino == ino_tmp->inode)
                add_special_proc(ino_tmp->name, PTYPE_MOUNT, 0, find_mountp);
        }
    }
    fclose(fp);
}

static void scan_swaps(
    struct names *names_head,
    struct inode_list *ino_head,
    struct device_list *dev_head)
{
    struct device_list *dev_tmp;
    struct inode_list *ino_tmp;
    FILE *fp;
    char line[BUFSIZ];
    char *find_space;
    struct stat st;

    if ( (ino_head == NULL) && (dev_head == NULL) )
        return;

    if ((fp = fopen(PROC_SWAPS, "r")) == NULL)
    {
        /*fprintf(stderr, "Cannot open %s\n", PROC_SWAPS); */
        return;
    }
    /* lines are filename   type */
    while (fgets(line, BUFSIZ, fp) != NULL)
    {
        if ((find_space = strchr(line, ' ')) == NULL)
            continue;
        *find_space = '\0';
        find_space++;
        while (*find_space == ' ')
        {
            find_space++;
            if (*find_space == '\0')
                continue;
        }
        if (timeout(thestat, line, &st, 5) != 0)
            continue;

        /* Scan the devices */
        for (dev_tmp = dev_head; dev_tmp != NULL;
             dev_tmp = dev_tmp->next)
        {
            if (st.st_dev == dev_tmp->device)
                add_special_proc(dev_tmp->name, PTYPE_SWAP, 0, line);
        }
        for (ino_tmp = ino_head; ino_tmp != NULL;
             ino_tmp = ino_tmp->next)
        {
            if (st.st_dev == ino_tmp->device
                && st.st_ino == ino_tmp->inode)
                add_special_proc(ino_tmp->name, PTYPE_SWAP, 0, line);
        }
    }
    fclose(fp);
}

#if defined(WITH_MOUNTINFO_LIST)
/*
 * Use /proc/self/mountinfo of modern linux system to determine
 * the device numbers of the mount points. Use this to avoid the
 * stat(2) system call wherever possible.
 */

static list_t mntinfo = { &mntinfo, &mntinfo };

static void clear_mntinfo(void)
{
    list_t *ptr, *tmp;

    list_for_each_safe(ptr, tmp, &mntinfo)
    {
        mntinfo_t *mnt = list_entry(ptr, mntinfo_t);
        delete(ptr);
        free(mnt);
    }
}

static void init_mntinfo(void)
{
    char mpoint[PATH_MAX *4 + 1]; // octal escaping takes 4 chars per 1 char
    int mid, parid, max = 0;
    uint maj, min;
    list_t sort;
    FILE *mnt;

    if (!list_empty(&mntinfo))
        return;
    if ((mnt = fopen("/proc/self/mountinfo", "r")) == (FILE *) 0)
        return;
    while (fscanf
           (mnt, "%i %i %u:%u %*s %s %*[^\n]",
            &mid, &parid, &maj, &min, &mpoint[0]) == 5)
    {
        const size_t nlen = strlen(mpoint);
        mntinfo_t *restrict mnt;
        if (posix_memalign ((void *)&mnt, sizeof(void *),
                            alignof(mntinfo_t) + (nlen + 1)) != 0)
        {
            fprintf(stderr,
                _("Cannot allocate memory for matched proc: %s\n"),
                strerror(errno));
            exit(1);
        }
        append(mnt, mntinfo);
        mnt->mpoint = ((char *)mnt) + alignof(mntinfo_t);
        strcpy(mnt->mpoint, mpoint);
        mnt->nlen = nlen;
        mnt->parid = parid;
        mnt->dev = makedev(maj, min);
        mnt->id = mid;
        if (mid > max)
            max = mid;
    }
    fclose(mnt);

    /* Sort mount points accordingly to the reverse mount order */
    initial(&sort);
    for (mid = 1; mid <= max; mid++)
    {
        list_t *ptr, *tmp;
        list_for_each_safe(ptr, tmp, &mntinfo)
        {
            mntinfo_t *mnt = list_entry(ptr, mntinfo_t);
            if (mid != mnt->id)
                continue;
            move_head(ptr, &sort);
            break;
        }
        list_for_each_safe(ptr, tmp, &mntinfo)
        {
            mntinfo_t *mnt = list_entry(ptr, mntinfo_t);
            if (mid != mnt->parid)
                continue;
            move_head(ptr, &sort);
        }
    }
    if (!list_empty(&mntinfo))
    {
#ifdef EBADE
        errno = EBADE;
#else
        errno = ENOENT;
#endif                /* EBADE */
    }
    join(&sort, &mntinfo);
}

/*
 * Determine device of links below /proc/
 */
static int mntstat(
    const char *path,
    struct stat *buf)
{
    char name[PATH_MAX + 1];
    const char *use;
    ssize_t nlen;
    list_t *ptr;

    if ((use = realpath(path, name)) == NULL || *use != '/')
    {
        if (errno == ENOENT)
            return -1;
        /*
         * Could be a special file (socket, pipe, inotify)
         */
        errno = 0;
        return stat(path, buf);
    }

    nlen = strlen(use);
    list_for_each(ptr, &mntinfo)
    {
        mntinfo_t *mnt = list_entry(ptr, mntinfo_t);
        if (nlen < mnt->nlen)
            continue;
        if (mnt->nlen == 1) /* root fs is the last entry */
        {
            buf->st_dev = mnt->dev;
            buf->st_ino = 0;
            return 0;
        }
        if (use[mnt->nlen] != '\0' && use[mnt->nlen] != '/')
            continue;
        if (strncmp(use, mnt->mpoint, mnt->nlen) == 0)
        {
            buf->st_dev = mnt->dev;
            buf->st_ino = 0;
            return 0;
        }
    }
    errno = ENOENT;
    return -1;
}
#endif                /* WITH_MOUNTINFO_LIST */

/*
 * Somehow the realpath(3) glibc function call, nevertheless
 * it avoids lstat(2) system calls.
 */
static char real[PATH_MAX + 1];
char *expandpath(
    const char *path)
{
    char tmpbuf[PATH_MAX + 1];
    const char *start, *end;
    char *curr, *dest;
    int deep = MAXSYMLINKS;

    if (!path || *path == '\0')
        return (char *)0;

    curr = &real[0];

    if (*path != '/')
    {
        if (!getcwd(curr, PATH_MAX))
            return (char *)0;
#ifdef HAVE_RAWMEMCHR
        dest = rawmemchr(curr, '\0');
#else
        dest = strchr(curr, '\0');
#endif
    } else {
        *curr = '/';
        dest = curr + 1;
    }

    for (start = end = path; *start; start = end)
    {
        while (*start == '/')
            ++start;

        for (end = start; *end && *end != '/'; ++end) ;

        if (end - start == 0)
            break;
        else if (end - start == 1 && start[0] == '.')
        {
            ;
        } else if (end - start == 2 && start[0] == '.' && start[1] == '.')
        {
            if (dest > curr + 1)
                while ((--dest)[-1] != '/') ;
        } else {
            char lnkbuf[PATH_MAX + 1];
            size_t len;
            ssize_t n;

            if (dest[-1] != '/')
                *dest++ = '/';

            if (dest + (end - start) > curr + PATH_MAX)
            {
                errno = ENAMETOOLONG;
                return (char *)0;
            }

            dest = mempcpy(dest, start, end - start);
            *dest = '\0';

            if (deep-- < 0)
            {
                errno = ELOOP;
                return (char *)0;
            }

            errno = 0;
            if ((n = readlink(curr, lnkbuf, PATH_MAX)) < 0)
            {
                deep = MAXSYMLINKS;
                if (errno == EINVAL)
                    continue;    /* Not a symlink */
                return (char *)0;
            }
            lnkbuf[n] = '\0';    /* Don't be fooled by readlink(2) */

            len = strlen(end);
            if ((n + len) > PATH_MAX)
            {
                errno = ENAMETOOLONG;
                return (char *)0;
            }

            memmove(&tmpbuf[n], end, len + 1);
            path = end = memcpy(tmpbuf, lnkbuf, n);

            if (lnkbuf[0] == '/')
                dest = curr + 1;
            else if (dest > curr + 1)
                while ((--dest)[-1] != '/') ;

        }
    }

    if (dest > curr + 1 && dest[-1] == '/')
        --dest;
    *dest = '\0';

    return curr;
}
