/*
 * dfile.c - Solaris file processing functions for lsof
 */

/*
 * Copyright 1994 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 1994 Purdue Research Foundation.\nAll rights reserved.\n";
#endif

#include "common.h"

/*
 * Local structures
 */

/*
 * Local static variables
 */

/*
 * Local definitions
 */

#define SFCDHASH 1024 /* Sfile hash by clone device */
#define SFDIHASH                                                               \
    4094 /* Sfile hash by (device,inode) number                                \
          * pair bucket count (power of 2!) */
#define SFFSHASH                                                               \
    128 /* Sfile hash by file system device                                    \
         * number bucket count (power of 2!) */
#define SFHASHDEVINO(maj, min, ino, mod)                                       \
    ((int)(((int)((((int)(maj + 1)) * ((int)((min + 1)))) + ino) * 31415) &    \
           (mod - 1)))
/* hash for Sfile by major device,
 * minor device, and inode, modulo m
 * (m must be a power of 2) */
#define SFNMHASH                                                               \
    4096 /* Sfile hash by name bucket count                                    \
  (power of 2!) */
#define SFRDHASH                                                               \
    1024 /* Sfile hash by raw device number                                    \
          * bucket count (power of 2!) */
#define SFHASHRDEVI(maj, min, rmaj, rmin, ino, mod)                            \
    ((int)(((int)((((int)(maj + 1)) * ((int)((min + 1)))) +                    \
                  ((int)(rmaj + 1) * (int)(rmin + 1)) + ino) *                 \
            31415) &                                                           \
           (mod - 1)))
/* hash for Sfile by major device,
 * minor device, major raw device,
 * minor raw device, and inode, modulo
 * mod (mod must be a power of 2) */

#if solaris < 20500
/*
 * 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);
}
#endif /* solaris<20500 */

/*
 * hashSfile() - hash Sfile entries for use in is_file_named() searches
 */

void hashSfile(struct lsof_context *ctx) {
    int cmaj, hvc, i;
    static int hs = 0;
    struct sfile *s;
    struct hsfile *sh, *sn;
    /*
     * Do nothing if there are no file search arguments cached or if the
     * hashes have already been constructed.
     */
    if (!Sfile || hs)
        return;
    /*
     * Preset the clone major device for Solaris.
     */
    if (HaveCloneMaj) {
        cmaj = CloneMaj;
        hvc = 1;
    } else
        hvc = 0;
    /*
     * Allocate hash buckets by clone device, (device,inode), file system
     * device, and file name.
     */
    if (hvc) {
        if (!(HbyCd = (struct hsfile *)calloc((MALLOC_S)SFCDHASH,
                                              sizeof(struct hsfile)))) {
            (void)fprintf(
                stderr, "%s: can't allocate space for %d clone hash buckets\n",
                Pn, SFCDHASH);
            Error(ctx);
        }
    }
    if (!(HbyFdi = (struct hsfile *)calloc((MALLOC_S)SFDIHASH,
                                           sizeof(struct hsfile)))) {
        (void)fprintf(
            stderr, "%s: can't allocate space for %d (dev,ino) hash buckets\n",
            Pn, SFDIHASH);
        Error(ctx);
    }
    if (!(HbyFrd = (struct hsfile *)calloc((MALLOC_S)SFRDHASH,
                                           sizeof(struct hsfile)))) {
        (void)fprintf(stderr,
                      "%s: can't allocate space for %d rdev hash buckets\n", Pn,
                      SFRDHASH);
        Error(ctx);
    }
    if (!(HbyFsd = (struct hsfile *)calloc((MALLOC_S)SFFSHASH,
                                           sizeof(struct hsfile)))) {
        (void)fprintf(stderr,
                      "%s: can't allocate space for %d file sys hash buckets\n",
                      Pn, SFFSHASH);
        Error(ctx);
    }
    if (!(HbyNm = (struct hsfile *)calloc((MALLOC_S)SFNMHASH,
                                          sizeof(struct hsfile)))) {
        (void)fprintf(stderr,
                      "%s: can't allocate space for %d name hash buckets\n", Pn,
                      SFNMHASH);
        Error(ctx);
    }
    hs++;
    /*
     * Scan the Sfile chain, building file, file system, and file name hash
     * bucket chains.
     */
    for (s = Sfile; s; s = s->next) {
        for (i = 0; i < 4; i++) {
            if (i == 0) {
                if (!s->aname)
                    continue;
                sh = &HbyNm[hashbyname(s->aname, SFNMHASH)];
                HbyNmCt++;
            } else if (i == 1) {
                if (s->type) {
                    sh = &HbyFdi[SFHASHDEVINO(GET_MAJ_DEV(s->dev),
                                              GET_MIN_DEV(s->dev), s->i,
                                              SFDIHASH)];
                    HbyFdiCt++;
                } else {
                    sh = &HbyFsd[SFHASHDEVINO(
                        GET_MAJ_DEV(s->dev), GET_MIN_DEV(s->dev), 0, SFFSHASH)];
                    HbyFsdCt++;
                }
            } else if (i == 2) {
                if (s->type && ((s->mode == S_IFCHR) || (s->mode == S_IFBLK))) {
                    sh = &HbyFrd[SFHASHRDEVI(
                        GET_MAJ_DEV(s->dev), GET_MIN_DEV(s->dev),
                        GET_MAJ_DEV(s->rdev), GET_MIN_DEV(s->rdev), s->i,
                        SFRDHASH)];
                    HbyFrdCt++;
                } else
                    continue;
            } else {
                if (!hvc || (GET_MAJ_DEV(s->rdev) != cmaj))
                    continue;
                sh = &HbyCd[SFHASHDEVINO(0, GET_MIN_DEV(s->rdev), 0, SFCDHASH)];
                HbyCdCt++;
            }
            if (!sh->s) {
                sh->s = s;
                sh->next = (struct hsfile *)NULL;
                continue;
            } else {
                if (!(sn = (struct hsfile *)malloc(
                          (MALLOC_S)sizeof(struct hsfile)))) {
                    (void)fprintf(stderr,
                                  "%s: can't allocate hsfile bucket for: %s\n",
                                  Pn, s->aname);
                    Error(ctx);
                }
                sn->s = s;
                sn->next = sh->next;
                sh->next = sn;
            }
        }
    }
}

/*
 * is_file_named() - is this file named?
 */

int is_file_named(struct lsof_context *ctx, /* context */
                  char *p,       /* path name; NULL = search by device
                                  * and inode (from *Lf) */
                  int nt,        /* node type -- e.g., N_* */
                  enum vtype vt, /* vnode type */
                  int ps)        /* print status: 0 = don't copy name
                                  * to Namech */
{
    char *ep;
    int f = 0;
    struct sfile *s;
    struct hsfile *sh;
    size_t sz;
    /*
     * Check for a path name match, as requested.
     */
    if (p && HbyNmCt) {
        for (sh = &HbyNm[hashbyname(p, SFNMHASH)]; sh; sh = sh->next) {
            if ((s = sh->s) && strcmp(p, s->aname) == 0) {
                f = 2;
                break;
            }
        }
    }
    /*
     * Check for a Solaris clone file.
     */
    if (!f && HbyCdCt && nt == N_STREAM && Lf->dev_def && Lf->rdev_def &&
        (Lf->dev == DevDev)) {
        for (sh = &HbyCd[SFHASHDEVINO(0, GET_MAJ_DEV(Lf->rdev), 0, SFCDHASH)];
             sh; sh = sh->next) {
            if ((s = sh->s) &&
                (GET_MAJ_DEV(Lf->rdev) == GET_MIN_DEV(s->rdev))) {
                f = 1;
                break;
            }
        }
    }
    /*
     * Check for a regular file.
     */
    if (!f && HbyFdiCt && Lf->dev_def && (Lf->inp_ty == 1 || Lf->inp_ty == 3)) {
        for (sh = &HbyFdi[SFHASHDEVINO(GET_MAJ_DEV(Lf->dev),
                                       GET_MIN_DEV(Lf->dev), Lf->inode,
                                       SFDIHASH)];
             sh; sh = sh->next) {
            if ((s = sh->s) && (Lf->dev == s->dev) && (Lf->inode == s->i)) {
                f = 1;
                break;
            }
        }
    }
    /*
     * Check for a file system match.
     */
    if (!f && HbyFsdCt && Lf->dev_def) {
        for (sh = &HbyFsd[SFHASHDEVINO(GET_MAJ_DEV(Lf->dev),
                                       GET_MIN_DEV(Lf->dev), 0, SFFSHASH)];
             sh; sh = sh->next) {
            if ((s = sh->s) && Lf->dev == s->dev) {
                f = 1;
                break;
            }
        }
    }
    /*
     * Check for a character or block device match.
     */
    if (!f && HbyFrdCt && ((vt = VCHR) || (vt = VBLK)) && Lf->dev_def &&
        (Lf->dev == DevDev) && Lf->rdev_def &&
        (Lf->inp_ty == 1 || Lf->inp_ty == 3)) {
        for (sh = &HbyFrd[SFHASHRDEVI(
                 GET_MAJ_DEV(Lf->dev), GET_MIN_DEV(Lf->dev),
                 GET_MAJ_DEV(Lf->rdev), GET_MIN_DEV(Lf->rdev), Lf->inode,
                 SFRDHASH)];
             sh; sh = sh->next) {
            if ((s = sh->s) && (s->dev == Lf->dev) && (s->rdev == Lf->rdev) &&
                (s->i == Lf->inode)) {
                f = 1;
                break;
            }
        }
    }
    /*
     * Convert the name if a match occurred.
     */
    if (f) {
        if (f == 2) {
            if (ps)
                (void)snpf(Namech, Namechl, "%s", p);
        } else {
            if (ps && s->type) {

                /*
                 * If the search argument isn't a file system, propagate it
                 * to Namech[]; otherwise, let printname() compose the name.
                 */
                (void)snpf(Namech, Namechl, "%s", s->name);
                if (s->devnm) {
                    ep = endnm(ctx, &sz);
                    (void)snpf(ep, sz, " (%s)", s->devnm);
                }
            }
        }
        s->f = 1;
        return (1);
    }
    return (0);
}

#if defined(HASPRINTDEV)
/*
 * 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];
    /*
     * Avoid the Solaris major() and minor() functions from makedev(3C) to get
     * printable major/minor numbers.
     *
     * We would like to use the L_MAXMAJ definition from <sys/sysmacros.h> all
     * the time, but it's not always correct in all versions of Solaris.
     */
    (void)snpf(buf, sizeof(buf), "%d,%d",
               (int)((*dev >> L_BITSMINOR) &

#    if solaris >= 20501
                     L_MAXMAJ
#    else  /* solaris<20501 */
                     0x3fff
#    endif /* solaris>=20501 */

                     ),
               (int)(*dev & L_MAXMIN));
    return (buf);
}
#endif /* defined(HASPRINTDEV) */

#if defined(HAS_V_PATH)

/*
 * Local definitions
 */

#    define VPRDLEN ((MAXPATHLEN + 7) / 8) /* v_path read length increment */

/*
 * print_v_path() - print path name from vnode's v_path pointer
 */

extern int print_v_path(struct lsof_context *ctx,
                        struct lfile *lf) /* local file structure */
{
    char buf[MAXPATHLEN + 1];
    unsigned char del = 0;
    unsigned char aperr = 0;

#    if defined(HASMNTSTAT)
    struct stat sb;
#    endif /* defined(HASMNTSTAT) */

#    if defined(HASVXFS) && defined(HASVXFSRNL)
    if (lf->is_vxfs && (lf->inp_ty == 1) && lf->fsdir) {
        if (print_vxfs_rnl_path(lf))
            return (1);
    }
#    endif /* defined(HASVXFS) && defined(HASVXFSRNL) */

    (void)read_v_path(ctx, (KA_T)lf->V_path, buf, (size_t)sizeof(buf));
    if (buf[0]) {

#    if defined(HASMNTSTAT)
        if (!lf->mnt_stat && lf->dev_def && (lf->inp_ty == 1)) {

            /*
             * No problem was detected in applying stat(2) to this mount point.
             * If the device and inode for the file are known, it is probably
             * safe and worthwhile to apply stat(2) to the v_path.
             */
            if (!statsafely(ctx, buf, &sb)) {

                /*
                 * The stat(2) succeeded.  See if the device and inode match.
                 * If they both don't match, ignore the v_path.
                 */
                if ((lf->dev != sb.st_dev) ||
                    (lf->inode != (INODETYPE)sb.st_ino)) {
                    return (0);
                }
            } else {

                /*
                 * The stat(2) failed.
                 *
                 * If the error reply is ENOENT and the -X option hasn't been
                 * specified, ignore the v_path.
                 *
                 * If the error reply is ENOENT, the -X option has been
                 * specified and the file's link count is zero, report the
                 * v_path with the "(deleted)" notation.
                 *
                 * If the error reply is EACCES or EPERM, report the v_path,
                 * followed by "(?)", because lsof probably lacks permission
                 * to apply stat(2) to v_path.
                 */
                switch (errno) {
                case EACCES:
                case EPERM:
                    aperr = 1;
                    break;
                case ENOENT:

#        if defined(HASXOPT)
                    if (Fxopt && lf->nlink_def && !lf->nlink) {
                        del = 1;
                        break;
                    }
#        endif /* defined(HASXOPT) */

                    return (0);
                default:
                    return (0);
                }
            }
        }
#    endif /* defined(HASMNTSTAT) */

        /*
         * Print the v_path.
         */
        safestrprt(buf, stdout, 0);
        if (del)
            safestrprt(" (deleted)", stdout, 0);
        else if (aperr)
            safestrprt(" (?)", stdout, 0);
        return (1);
    }
    return (0);
}

/*
 * read_v_path() - read path name from vnode's v_path pointer
 */

extern void read_v_path(struct lsof_context *ctx,
                        KA_T ka,    /* kernel path address */
                        char *rb,   /* receiving buffer */
                        size_t rbl) /* receiving buffer length */
{
    char *ba;
    size_t rl, tl;

    *rb = '\0';
    if (!ka)
        return;
    for (ba = rb, tl = 0; tl < (rbl - 1);
         ba += rl, ka += (KA_T)((char *)ka + rl), tl += rl) {

        /*
         * Read v_path VPRDLEN bytes at a time until the local buffer is full
         * or a NUL byte is reached.
         */
        if ((rl = rbl - 1 - tl) > VPRDLEN)
            rl = VPRDLEN;
        else if (rl < 1) {
            *(rb + rbl - 1) = '\0';
            break;
        }
        if (!kread(ctx, ka, ba, rl)) {
            *(ba + rl) = '\0';
            if (strchr(ba, '\0') < (ba + rl))
                break;
        } else {

            /*
             * Can't read a full buffer load; try reducing the length one
             * byte at a time until it reaches zero.  Stop here, since it
             * has been established that no more bytes can be read.
             */
            for (rl--; rl > 0; rl--) {
                if (!kread(ctx, ka, ba, rl)) {
                    *(ba + rl) = '\0';
                    break;
                }
            }
            if (rl <= 0)
                *ba = '\0';
            break;
        }
    }
}
#endif /* defined(HAS_V_PATH) */

/*
 * process_file() - process file
 */

void process_file(struct lsof_context *ctx, /* context */
                  KA_T fp)                  /* kernel file structure address */
{
    struct file f;
    int flag;

#if defined(FILEPTR)
    FILEPTR = &f;
#endif /* defined(FILEPTR) */

    if (kread(ctx, fp, (char *)&f, sizeof(f))) {
        (void)snpf(Namech, Namechl, "can't read file struct from %s",
                   print_kptr(fp, (char *)NULL, 0));
        enter_nm(ctx, Namech);
        return;
    }
    Lf->off = (SZOFFTYPE)f.f_offset;
    Lf->off_def = 1;

    if (f.f_count) {

        /*
         * Construct access code.
         */
        if ((flag = (f.f_flag & (FREAD | FWRITE))) == FREAD)
            Lf->access = LSOF_FILE_ACCESS_READ;
        else if (flag == FWRITE)
            Lf->access = LSOF_FILE_ACCESS_WRITE;
        else if (flag == (FREAD | FWRITE))
            Lf->access = LSOF_FILE_ACCESS_READ_WRITE;

#if defined(HASFSTRUCT)
        /*
         * Save file structure values.
         */
        Lf->fct = (long)f.f_count;
        Lf->fsv |= FSV_CT;
        Lf->fsa = fp;
        Lf->fsv |= FSV_FA;
        Lf->ffg = (long)f.f_flag;
        Lf->fsv |= FSV_FG;
        Lf->fna = (KA_T)f.f_vnode;
        Lf->fsv |= FSV_NI;
#endif /* defined(HASFSTRUCT) */

        /*
         * Solaris file structures contain a vnode pointer.  Process it.
         */
        process_node(ctx, (KA_T)f.f_vnode);
        return;
    }
    enter_nm(ctx, "no more information");
}

#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 RFC2553-compatible getipnodebyname() function
 */

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

    if (hep)
        (void)freehostent(hep);
    return ((hep = getipnodebyname(nm, prot, 0, &err)));
}
#endif /* defined(HASIPv6) */
