/*
 * dfile.c -- pstat-based HP-UX file functions for lsof
 */

/*
 * Copyright 1999 Purdue Research Foundation, West Lafayette, Indiana
 * 47907.  All rights reserved.
 *
 * Written by Victor A. Abell
 *
 * This software is not subject to any license of the American Telephone
 * and Telegraph Company or the Regents of the University of California.
 *
 * Permission is granted to anyone to use this software for any purpose on
 * any computer system, and to alter it and redistribute it freely, subject
 * to the following restrictions:
 *
 * 1. Neither the authors nor Purdue University are responsible for any
 *    consequences of the use of this software.
 *
 * 2. The origin of this software must not be misrepresented, either by
 *    explicit claim or by omission.  Credit to the authors and Purdue
 *    University must appear in documentation and sources.
 *
 * 3. Altered versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 *
 * 4. This notice may not be removed or altered.
 */

#ifndef lint
static char copyright[] =
    "@(#) Copyright 1999 Purdue Research Foundation.\nAll rights reserved.\n";
#endif

#include "common.h"

#if defined(HASNCACHE)
/*
 * Local definitions
 */

#    define DNLCINCR 2048 /* DNLC read increment */

#    define NFSIDH                                                             \
        256 /* file system ID hash count                                       \
             * -- MUST BE A POWER OF TWO!!! */
#    define NFSID sizeof(struct psfsid)
/* size of psfsid structure */
#    define NL_NC sizeof(struct l_nc)
/* size of l_nc structure */
#    define NPSTM sizeof(struct pst_mpathnode)
/* size of pst_mpathnode */

/*
 * Local structure definitions
 */

struct l_nc {            /* local name cache */
    struct psfileid id;  /* node's PSTAT ID */
    struct psfileid par; /* parent's PSTAT ID */
    struct l_nc *pl;     /* local parent name cache entry --
                          * NULL if not found or not yet
                          * accessed (see pls) */
    int pls;             /* status of pl: 0 = not accessed
                          *		 1 = accessed */
    int nl;              /* name length */
    char *nm;            /* name */
    struct l_nc *next;   /* next hash bucket link */
};

struct l_fic {          /* file system ID cache */
    struct psfsid fsid; /* file system ID */
    int nc;             /* files cached for file system */
    struct l_fic *next; /* next hash bucket link */
};

/*
 * Local static variables
 */

static int Nceh; /* number of Nchash entries allocated */
static struct l_nc **Nchash = (struct l_nc **)NULL;
/* the name cache hash buckets */
static int Ncmask; /* power of two mask for the name
                    * cache -- sized from Nc */
static int Ndnlc;  /* number of DNLC entries via
                    * pst_dynamic.psd_dnlc_size */
static struct l_fic **Ncfsid = (struct l_fic **)NULL;
/* the file system hash buckets */
static struct pst_fid Nzpf;  /* zeroed pst_fid (for memcmp()) */
static struct psfileid Nzps; /* zeroed psfilid (for memcmp()) */
static int Nzpfs = 0;        /* Nzpf status: 1 = zeroed */
static int Nzpss = 0;        /* Nzps status: 1 = zeroed */

/*
 * Local macros
 */

#    define HASHFSID(i)                                                        \
        (Ncfsid +                                                              \
         (((int)(((((struct psfsid *)i)->psfs_id * 31415) << 3) & 0xfffffff) + \
           (int)((((((struct psfsid *)i)->psfs_type * 31415) << 5) &           \
                  0xfffffff))) &                                               \
          (NFSIDH - 1)))
#    define HASHPSFILEID(p)                                                    \
        (Nchash +                                                              \
         (((int)(((int)((((struct psfileid *)p)->psf_fsid.psfs_id * 31415)     \
                        << 3) &                                                \
                  0xfffffff) +                                                 \
                 (int)(((((struct psfileid *)p)->psf_fsid.psfs_type * 31415)   \
                        << 5) &                                                \
                       0xfffffff) +                                            \
                 (int)(((((struct psfileid *)p)->psf_fileid * 31415) << 7) &   \
                       0xfffffff))) &                                          \
          Ncmask))

/*
 * Local function prototypes
 */

static struct l_nc *ncache_addr(struct psfileid *ps);
static void ncache_free(void);
static int ncache_isroot(struct psfileid *ps);
static void ncache_size(void);
#endif /* defined(HASNCACHE) */

#if defined(HASIPv6)
/*
 * gethostbyname2() -- an RFC2133-compatible get-host-by-name-two function
 *                     to get AF_INET and AF_INET6 addresses from host names,
 *                     using the gethostbyname() and RFC2553-compatible
 *		       getipnodebyname() functions
 */

extern struct hostent *
gethostbyname2(char *nm, /* host name */
               int prot) /* protocol -- AF_INET or AF_INET6 */
{
    int err;

    if (prot == AF_INET) {

        /*
         * This shouldn't be necessary if /etc/nsswitch.conf is correct, but
         * it's a good fail-safe in case /etc/nsswitch.conf is missing or
         * incorrect.
         */
        return (gethostbyname(nm));
    }
    return (getipnodebyname(nm, prot, 0, &err));
}
#endif /* defined(HASIPv6) */

/*
 * get_max_fd() -- get maximum file descriptor plus one
 */

int get_max_fd() {
    struct rlimit r;

    if (getrlimit(RLIMIT_NOFILE, &r))
        return (-1);
    return (r.rlim_cur);
}

#if defined(HASNCACHE)

/*
 * ncache_addr() -- get ncache entry address
 */

static struct l_nc *ncache_addr(struct psfileid *ps) /* parent's psfileid */
{
    struct l_nc **hp, *lc;

    for (hp = HASHPSFILEID(ps), lc = *hp; lc; lc = lc->next) {
        if (!memcmp((void *)ps, (void *)&lc->id, sizeof(struct psfileid)))
            return (lc);
    }
    return ((struct l_nc *)NULL);
}

/*
 * ncache_alloc() -- allocate name cache space
 */

static void ncache_alloc() {
    if (Nchash || Ncfsid)
        ncache_free();
    (void)ncache_size();
    if (!(Nchash = (struct l_nc **)calloc(Nceh, sizeof(struct l_nc *)))) {
        (void)fprintf(stderr,
                      "%s: can't allocate %d local name cache entries\n", Pn,
                      Nceh);
        Error(ctx);
    }
    if (Ncfsid)
        return;
    if (!(Ncfsid = (struct l_fic **)calloc(NFSIDH, sizeof(struct l_fic *)))) {
        (void)fprintf(stderr,
                      "%s: can't allocate %d local file system cache entries\n",
                      Pn, NFSIDH);
        Error(ctx);
    }
}

/*
 * ncache_free() -- free previous ncache allocations
 */

static void ncache_free() {
    int i;
    struct l_fic **fh, *fp, *fx;
    struct l_nc **nh, *np, *nx;

    if (Ncfsid) {

        /*
         * Free file system ID hash bucket contents.
         */
        for (fh = Ncfsid, i = 0; i < NFSIDH; fh++, i++) {
            for (fp = *fh; fp; fp = fx) {
                fx = fp->next;
                (void)free((MALLOC_P *)fp);
            }
            Ncfsid[i] = (struct l_fic *)NULL;
        }
    }
    if (Nchash) {

        /*
         * Free name cache.
         */
        for (i = 0, nh = Nchash; i < Nceh; i++, nh++) {
            for (np = *nh; np; np = nx) {
                nx = np->next;
                if (np->nm)
                    (void)free((MALLOC_P *)np->nm);
                (void)free((MALLOC_P *)np);
            }
        }
        (void)free((MALLOC_P *)Nchash);
        Nchash = (struct l_nc **)NULL;
    }
}

/*
 * ncache_isroot() -- does psfileid represent the root of a file system?
 */

static int ncache_isroot(struct psfileid *ps) /* psfileid */
{
    if (!ps->psf_fsid.psfs_id && !ps->psf_fsid.psfs_type &&
        ps->psf_fileid == -1)
        return (1);

#    if defined(HASFSINO)
    if (!Lf->fs_ino || (Lf->inp_ty != 1) || !Lf->dev_def)
        return (0);
    if ((Lf->dev == (dev_t)ps->psf_fsid.psfs_id) &&
        (Lf->fs_ino == (unsigned long)ps->psf_fileid))
        return (1);
#    endif /* defined(HASFSINO) */

    return (0);
}

/*
 * ncache_load() -- load name cache
 */

void ncache_load() {
    if (!Fncache)
        return;
    (void)ncache_alloc();
    if (!Nzpfs) {
        (void)memset((void *)&Nzpf, 0, sizeof(Nzpf));
        Nzpfs = 1;
    }
    if (!Nzpss) {
        (void)memset((void *)&Nzps, 0, sizeof(Nzps));
        Nzpss = 1;
    }
}

/*
 * ncache_loadfs() -- load the name cache for a file system
 */

struct l_fic *ncache_loadfs(struct psfsid *fsid, /* ID of file system to add */
                            struct l_fic **fh)   /* Ncfsid hash bucket */
{
    char *cp;
    struct l_fic *f;
    int i, nl, nr;
    struct pst_mpathnode mp[DNLCINCR];
    struct l_nc **nh, *nn, *nt, *ntp;
    int x = 0;
    /*
     * Allocate a new file system pointer structure and link it to its bucket.
     */
    if (!(f = (struct l_fic *)malloc(sizeof(struct l_fic)))) {
        (void)fprintf(stderr, "%s: no fsid structure space\n", Pn);
        Error(ctx);
    }
    f->fsid = *fsid;
    f->nc = 0;
    f->next = *fh;
    *fh = f;
    while ((nr = pstat_getmpathname(&mp[0], NPSTM, DNLCINCR, x, fsid)) > 0) {
        x = mp[nr - 1].psr_idx + 1;
        for (i = 0; i < nr; i++) {

            /*
             * Ignore NUL names, ".", and "..".
             */
            if (!(nl = (int)strlen(mp[i].psr_name)))
                continue;
            if ((nl < 3) && (mp[i].psr_name[0] == '.')) {
                if ((nl == 1) || (mp[i].psr_name[1] == '.'))
                    continue;
            }
            /*
             * Allocate name and name cache structure space.
             */
            if (!(cp = (char *)malloc((MALLOC_S)(nl + 1)))) {
                (void)fprintf(stderr, "%s: no name entry space (%d) for:%s\n",
                              Pn, nl + 1, mp[i].psr_name);
                Error(ctx);
            }
            if (!(nn = (struct l_nc *)malloc(sizeof(struct l_nc)))) {
                (void)fprintf(stderr,
                              "%s: no name cache entry space (%d) for: %s\n",
                              Pn, (int)sizeof(struct l_nc), mp[i].psr_name);
                Error(ctx);
            }
            /*
             * Fill in name cache entry, complete with name and name length.
             */
            (void)snpf(cp, nl + 1, "%s", mp[i].psr_name);
            nn->id = mp[i].psr_file;
            nn->par = mp[i].psr_parent;
            nn->nm = cp;
            nn->nl = nl;
            nn->pl = nn->next = (struct l_nc *)NULL;
            nn->pls = 0;
            nh = HASHPSFILEID(&mp[i].psr_file);
            /*
             * Skip to the end of the hash bucket chain, looking for
             * duplicates along the way.
             */
            for (nt = *nh, ntp = (struct l_nc *)NULL; nt;
                 ntp = nt, nt = nt->next) {
                if (memcmp((void *)&nt->id, (void *)&nn->id, NL_NC) == 0)
                    break;
            }
            if (nt) {

                /*
                 * Remove a duplicate.
                 */
                if (ntp)
                    ntp = nt->next;
                else
                    *nh = nt->next;
                (void)free((MALLOC_P *)nt->nm);
                (void)free((MALLOC_P *)nt);
                (void)free((MALLOC_P *)nn->nm);
                (void)free((MALLOC_P *)nn);
            } else {

                /*
                 * Link a new entry.
                 */
                if (ntp)
                    ntp->next = nn;
                else
                    *nh = nn;
                f->nc++;
            }
        }
        if (nr < DNLCINCR)
            break;
    }
    return (f);
}

/*
 * ncache_lookup() -- look up a node's name in the kernel's name cache
 */

char *ncache_lookup(char *buf, /* receiving name buffer */
                    int blen,  /* receiving buffer length */
                    int *fp)   /* full path reply */
{
    char *cp = buf;
    int ef;
    struct l_fic **fh, *fs;
    struct l_nc *lc;
    int nl, rlen;
    char *pc;

    *cp = '\0';
    *fp = 0;

#    if defined(HASFSINO)
    /*
     * If the entry has an inode number that matches the inode number of the
     * file system mount point, return an empty path reply.  That tells the
     * caller that the already-printed system mount point name is sufficient.
     */
    if (Lf->inp_ty == 1 && Lf->fs_ino && Lf->inode == Lf->fs_ino)
        return (cp);
#    endif /* defined(HASFSINO) */

    /*
     * See if cache has been loaded for this pfsid.  Don't try to load if cache
     * loading has been inhibited with -C, or unless the real or effective UID
     * of this process is root.
     */
    if ((!Myuid || Setuidroot) && Fncache) {
        for (fh = HASHFSID(&Lf->psfid.psf_fsid), fs = *fh; fs; fs = fs->next) {
            if (memcmp((void *)&fs->fsid, (void *)&Lf->psfid.psf_fsid, NFSID) ==
                0)
                break;
        }
        if (!fs)
            fs = ncache_loadfs(&Lf->psfid.psf_fsid, fh);
    } else
        fs = (struct l_fic *)NULL;
    /*
     * Search the cache for an entry whose psfileid matches.
     */
    if (!fs || !fs->nc || !(lc = ncache_addr(&Lf->psfid))) {

        /*
         * If the node has no cache entry, see if it's the root of the file
         * system.
         */

#    if defined(HASFSINO)
        if (Lf->fs_ino && (Lf->inp_ty == 1) && (Lf->fs_ino == Lf->inode))
            return (cp);
#    endif /* defined(HASFSINO) */

        /*
         * If the file system's cache couldn't be loaded -- e.g., this lsof
         * process lacks permission to load it or cache lookup is inhibited
         * with -C -- but the UID of the file's process matches the UID of the
         * lsof process, see if it's possible to read the single path name for
         * this particular file.  (The file must have a non-zero opaque ID.)
         */
        if (!fs) {
            if (Fncache && (Myuid == Lp->uid) &&
                memcmp((void *)&Lf->opfid, (void *)&Nzpf, sizeof(Nzpf)) &&
                (nl = pstat_getpathname(buf, (blen - 1), &Lf->opfid)) > 0) {
                buf[nl] = '\0';
                if (*buf == '/')
                    *fp = 1;
                return (buf);
            }
        }
        return ((char *)NULL);
    }
    if (ncache_isroot(&lc->id)) {

        /*
         * If the node is the root of the file system, return a response
         * that will cause the root directory to be displayed.
         */
        return (cp);
    }
    /*
     * Start the path assembly.
     */
    if ((nl = lc->nl) > (blen - 1))
        return ((char *)NULL);
    cp = buf + blen - nl - 1;
    rlen = blen - nl - 1;
    (void)snpf(cp, nl + 1, "%s", lc->nm);
    /*
     * Look up the name cache entries that are parents of the node address.
     * Quit when:
     *
     *	there's no parent;
     *	the file system root is reached;
     *	the name length is too large to fit in the receiving buffer.
     */
    for (ef = 0; !ef;) {
        if (!lc->pl) {
            if (!lc->pls) {

                /*
                 * If there is a parent, look up its Ncache address;
                 * otherwise quit on an incomplete path assembly.
                 */
                if (memcmp((void *)&lc->par, (void *)&Nzps, sizeof(Nzps))) {
                    lc->pl = ncache_addr(&lc->par);
                    lc->pls = 1;
                } else
                    break;
            }
        }
        if (ncache_isroot(&lc->par)) {

            /*
             * If the parent entry is the file system root, enter the file
             * system root directory, and indicate that the assembly should
             * stop after this entry.
             */
            if (!(pc = Lf->fsdir))
                break;
            nl = (int)strlen(pc);
            ef = 1;
        } else {

            /*
             * Use the parent link if it exists; otherwise exit on an
             * incomplete path assembly.
             */
            if (!(lc = lc->pl))
                break;
            pc = lc->nm;
            nl = lc->nl;
        }
        /*
         * Prefix the next path component.  Intersperse a '/' if the
         * component doesn't end in one.
         */
        if (!nl)
            break;
        if (pc[nl - 1] != '/') {
            if (1 > rlen)
                break;
            *(cp - 1) = '/';
            cp--;
            rlen--;
        }
        if (nl > rlen)
            break;
        (void)strncpy((cp - nl), pc, nl);
        cp -= nl;
        rlen -= nl;
        if (ef) {

            /*
             * If the file system root directory was just prefixed, return
             * a full-path indication.
             */
            *fp = 1;
            break;
        }
    }
    return (cp);
}

/*
 * ncache_size() -- get DNLC size
 */

static void ncache_size() {
    struct pst_dynamic pd;

    if (pstat_getdynamic(&pd, sizeof(pd), 1, 0) != 1) {
        (void)fprintf(stderr, "%s: can't get dynamic status\n", Pn);
        Error(ctx);
    }
    Ndnlc = (int)pd.psd_dnlc_size;
    for (Nceh = 1; Nceh < (Ndnlc + Ndnlc); Nceh <<= 1)
        ;
    Ncmask = Nceh - 1;
}
#endif /* defined(HASNCACHE) */

/*
 * print_dev() -- print device
 */

char *print_dev(struct lfile *lf, /* file whose device is to be printed */
                dev_t *dev)       /* device to be printed */
{
    static char buf[128];

    (void)snpf(buf, sizeof(buf), "%d,%#x", GET_MAJ_DEV(*dev),
               GET_MIN_DEV(*dev));
    return (buf);
}

/*
 * process_finfo() -- process file information
 */

void process_finfo(pd, opfid, psfid,
                   na) struct pst_filedetails *pd; /* file details */
struct pst_fid *opfid;  /* opaque file ID for this file */
struct psfileid *psfid; /* PSTAT file ID for this file */
KA_T na;                /* node address */
{
    char *cp, buf[32];
    enum lsof_file_type type;
    uint32_t unknown_file_type_number = 0;
    dev_t dev;
    int devs = 0;
    int32_t lk;
    struct mounts *mp;
    /*
     * Save file IDs for later use in name lookup.
     */
    Lf->opfid = *opfid;
    Lf->psfid = *psfid;

#if defined(HASFSTRUCT)
    /*
     * Save node ID.
     */
    if (na) {
        Lf->fna = na;
        Lf->fsv |= FSV_NI;
    }
#endif /* defined(HASFSTRUCT) */

    /*
     * Construct lock code.
     */
    if ((lk = pd->psfd_lckflag) & PS_FPARTRDLCK)
        Lf->lock = LSOF_LOCK_READ_PARTIAL;
    else if (lk & PS_FPARTWRLCK)
        Lf->lock = LSOF_LOCK_WRITE_PARTIAL;
    else if (lk & PS_FFULLRDLCK)
        Lf->lock = LSOF_LOCK_READ_FULL;
    else if (lk & PS_FFULLWRLCK)
        Lf->lock = LSOF_LOCK_WRITE_FULL;
    else
        Lf->lock = LSOF_LOCK_NONE;
    /*
     * Derive type from modes.
     */
    switch ((int)(pd->psfd_mode & PS_IFMT)) {
    case PS_IFREG:
        type = LSOF_FILE_REGULAR;
        Ntype = N_REGLR;
        break;
    case PS_IFBLK:
        type = LSOF_FILE_BLOCK;
        Ntype = N_BLK;
        break;
    case PS_IFDIR:
        type = LSOF_FILE_DIR;
        Ntype = N_REGLR;
        break;
    case PS_IFCHR:
        type = LSOF_FILE_CHAR;
        Ntype = N_CHR;
        break;
    case PS_IFIFO:
        type = LSOF_FILE_FIFO;
        Ntype = N_FIFO;
        break;
    default:
        type = LSOF_FILE_UNKNOWN_RAW;
        unknown_file_type_number = (pd->psfd_mode & PS_IFMT) >> 12;
        Ntype = N_REGLR;
    }
    if (Lf->type == LSOF_FILE_NONE) {
        Lf->type = type;
        Lf->unknown_file_type_number = unknown_file_type_number;
    }
    Lf->ntype = Ntype;
    /*
     * Save device number.
     */
    switch (Ntype) {
    case N_FIFO:
        (void)enter_dev_ch(print_kptr(na, (char *)NULL, 0));
        break;
    default:
        dev = Lf->dev = (dev_t)pd->psfd_dev;
        devs = Lf->dev_def = 1;
        if ((Ntype == N_CHR) || (Ntype == N_BLK)) {
            Lf->rdev = (dev_t)pd->psfd_rdev;
            Lf->rdev_def = 1;
        }
    }
    /*
     * Save node number.
     */
    Lf->inode = (INODETYPE)pd->psfd_ino;
    Lf->inp_ty = 1;
    /*
     * Save link count.
     */

    /*
     * Ignore a zero link count only if the file is a FIFO.
     */
    if ((Lf->nlink = (long)pd->psfd_nlink) || (Ntype != N_FIFO))
        Lf->nlink_def = 1;
    if (Lf->nlink_def && Nlink && (Lf->nlink < Nlink))
        Lf->sf |= SELNLINK;
    /*
     * Save file system identity.
     */
    if (devs) {
        for (mp = readmnt(); mp; mp = mp->next) {
            if (dev == mp->dev) {
                Lf->fsdir = mp->dir;
                Lf->fsdev = mp->fsname;

#if defined(HASFSINO)
                Lf->fs_ino = (unsigned long)mp->inode;
#endif /* defined(HASFSINO) */

                break;
            }
        }
    } else
        mp = (struct mounts *)NULL;
    /*
     * If no offset has been activated and no size saved, activate the offset or
     * save the size.
     */
    if (!Lf->sz_def) {
        switch (Ntype) {
        case N_CHR:
        case N_FIFO:
            break;
        default:
            Lf->sz = (SZOFFTYPE)pd->psfd_size;
            Lf->sz_def = 1;
        }
    }
    /*
     * See if this is an NFS file.
     */
    if (Fnfs) {
        if (HasNFS < 0)
            (void)scanmnttab();
        if (HasNFS && mp && mp->is_nfs)
            Lf->sf |= SELNFS;
    }
    /*
     * Test for specified file.
     */
    if (Sfile &&
        is_file_named(NULL, ((Ntype == N_CHR) || (Ntype == N_BLK) ? 1 : 0)))
        Lf->sf |= SELNM;
    /*
     * Enter name characters.
     */
    if (!Lf->nm && Namech[0])
        enter_nm(Namech);
}
