/*
 * rdev.c -- readdev() function for lsof library
 */

/*
 * Copyright 1997 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.
 */

#include "common.h"
#include "machine.h"

#if defined(USE_LIB_READDEV)

static int rmdupdev(struct lsof_context *ctx, struct l_dev ***dp, int n,
                    char *nm);

/*
 * To use this source file:
 *
 * 1. Define DIRTYPE as:
 *
 *	  #define DIRTYPE direct
 *    or  #define DIRTYPE dirent
 *
 * 2. Define HASDNAMLEN if struct DIRTYPE has a d_namlen element, giving
 *    the length of d_name.
 *
 * 3. Define the RDEV_EXPDEV macro to apply special handling to device
 *    numbers, as required.  For example, for EP/IX 2.1.1:
 *
 *	#define RDEV_EXPDEV(n)	expdev(n)
 *
 *    to use the expdev() function to expand device numbers.  If
 *    no RDEV_EXPDEV macro is defined, it defaults to:
 *
 *	#define RDEV_EXPDEV(n)	(n)
 *
 * 4. Define HASBLKDEV to request that information on S_IFBLK devices be
 *    recorded in BDevtp[].
 *
 *    Define NOWARNBLKDEV to suppress the issuance of a warning when no
 *    block devices are found.
 *
 * 5. Define RDEV_STATFN to be a stat function other than stat() or lstat()
 *    -- e.g.,
 *
 *	#define	RDEV_STATFN	private_stat
 *
 * 6. Define HAS_STD_CLONE to request that clone device information be stored
 *    in standard clone structures (defined in lsof.h and addressed via
 *    Clone).  If HAS_STD_CLONE is defined, these must also be defined:
 *
 *	a.  Define CLONEMAJ to be the name of the constant or
 *	    variable that defines the clone major device -- e.g.,
 *
 *		#define CLONEMAJ CloneMaj
 *
 *	b.  Define HAVECLONEMAJ to be the name of the variable that
 *	    contains the status of the clone major device -- e.g.,
 *
 *		#define HAVECLONEMAJ HaveCloneMaj
 *
 *    Define HAS_STD_CLONE to be 1 if readdev() is expected to build the
 *    clone table, the clone table is cached (if HASDCACHE is defined), and
 *    there is a function to clear the cache table when the device table must
 *    be reloaded.  (See dvch.c for naming the clone cache build and clear
 *    functions.)
 */

#    if !defined(RDEV_EXPDEV)
#        define RDEV_EXPDEV(n) (n)
#    endif /* !defined(RDEV_EXPDEV) */

#    if !defined(RDEV_STATFN)
#        if defined(USE_STAT)
#            define RDEV_STATFN stat
#        else /* !defined(USE_STAT) */
#            define RDEV_STATFN lstat
#        endif /* defined(USE_STAT) */
#    endif     /* !defined(RDEV_STATFN) */

/*
 * readdev() - read device names, modes and types
 */

void readdev(struct lsof_context *ctx,
             int skip) /* skip device cache read if 1 */
{

#    if defined(HAS_STD_CLONE) && HAS_STD_CLONE == 1
    struct clone *c;
#    endif /* defined(HAS_STD_CLONE) && HAS_STD_CLONE==1 */

#    if defined(HASDCACHE)
    int dcrd;
#    endif /* defined(HASDCACHE) */

    DIR *dfp;
    int dnamlen;
    struct DIRTYPE *dp;
    char *fp = (char *)NULL;
    int i = 0;

#    if defined(HASBLKDEV)
    int j = 0;
#    endif /* defined(HASBLKDEV) */

    char *path = (char *)NULL;
    MALLOC_S pl;
    struct stat sb;

    if (Sdev)
        return;

#    if defined(HASDCACHE)
    /*
     * Read device cache, as directed.
     */
    if (!skip) {
        if (DCstate == 2 || DCstate == 3) {
            if ((dcrd = read_dcache(ctx)) == 0)
                return;
        }
    } else
        dcrd = 1;
#    endif /* defined(HASDCACHE) */

    Dstkn = Dstkx = 0;
    Dstk = (char **)NULL;
    (void)stkdir(ctx, "/dev");
    /*
     * Unstack the next /dev or /dev/<subdirectory> directory.
     */
    while (--Dstkx >= 0) {
        if (!(dfp = OpenDir(Dstk[Dstkx]))) {

#    if defined(WARNDEVACCESS)
            if (!Fwarn) {
                (void)fprintf(stderr, "%s: WARNING: can't open: ", Pn);
                safestrprt(Dstk[Dstkx], stderr, 1);
            }
#    endif /* defined(WARNDEVACCESS) */

            (void)free((FREE_P *)Dstk[Dstkx]);
            Dstk[Dstkx] = (char *)NULL;
            continue;
        }
        if (path) {
            (void)free((FREE_P *)path);
            path = (char *)NULL;
        }
        if (!(path =
                  mkstrcat(Dstk[Dstkx], -1, "/", 1, (char *)NULL, -1, &pl))) {
            (void)fprintf(stderr, "%s: no space for: ", Pn);
            safestrprt(Dstk[Dstkx], stderr, 1);
            Error(ctx);
        }
        (void)free((FREE_P *)Dstk[Dstkx]);
        Dstk[Dstkx] = (char *)NULL;
        /*
         * Scan the directory.
         */
        for (dp = ReadDir(dfp); dp; dp = ReadDir(dfp)) {
            if (dp->d_ino == 0 || dp->d_name[0] == '.')
                continue;
                /*
                 * Form the full path name and get its status.
                 */

#    if defined(HASDNAMLEN)
            dnamlen = (int)dp->d_namlen;
#    else  /* !defined(HASDNAMLEN) */
            dnamlen = (int)strlen(dp->d_name);
#    endif /* defined(HASDNAMLEN) */

            if (fp) {
                (void)free((FREE_P *)fp);
                fp = (char *)NULL;
            }
            if (!(fp = mkstrcat(path, pl, dp->d_name, dnamlen, (char *)NULL, -1,
                                (MALLOC_S *)NULL))) {
                (void)fprintf(stderr, "%s: no space for: ", Pn);
                safestrprt(path, stderr, 0);
                safestrprtn(dp->d_name, dnamlen, stderr, 1);
                Error(ctx);
            }
            if (RDEV_STATFN(fp, &sb) != 0) {
                if (errno == ENOENT) /* a sym link to nowhere? */
                    continue;

#    if defined(WARNDEVACCESS)
                if (!Fwarn) {
                    int errno_save = errno;

                    (void)fprintf(stderr, "%s: can't stat ", Pn);
                    safestrprt(fp, stderr, 0);
                    (void)fprintf(stderr, ": %s\n", strerror(errno_save));
                }
#    endif /* defined(WARNDEVACCESS) */

                continue;
            }
            /*
             * If it's a subdirectory, stack its name for later
             * processing.
             */
            if ((sb.st_mode & S_IFMT) == S_IFDIR) {
                (void)stkdir(ctx, fp);
                continue;
            }
            if ((sb.st_mode & S_IFMT) == S_IFCHR) {

                /*
                 * Save character device information in Devtp[].
                 */
                if (i >= Ndev) {
                    Ndev += DEVINCR;
                    if (!Devtp)
                        Devtp = (struct l_dev *)malloc(
                            (MALLOC_S)(sizeof(struct l_dev) * Ndev));
                    else
                        Devtp = (struct l_dev *)realloc(
                            (MALLOC_P *)Devtp,
                            (MALLOC_S)(sizeof(struct l_dev) * Ndev));
                    if (!Devtp) {
                        (void)fprintf(
                            stderr, "%s: no space for character device\n", Pn);
                        Error(ctx);
                    }
                }
                Devtp[i].rdev = RDEV_EXPDEV(sb.st_rdev);
                Devtp[i].inode = (INODETYPE)sb.st_ino;
                if (!(Devtp[i].name = mkstrcpy(fp, (MALLOC_S *)NULL))) {
                    (void)fprintf(stderr, "%s: no space for device name: ", Pn);
                    safestrprt(fp, stderr, 1);
                    Error(ctx);
                }
                Devtp[i].v = 0;

#    if defined(HAS_STD_CLONE) && HAS_STD_CLONE == 1
                if (HAVECLONEMAJ && GET_MAJ_DEV(Devtp[i].rdev) == CLONEMAJ) {

                    /*
                     * Record clone device information.
                     */
                    if (!(c = (struct clone *)malloc(sizeof(struct clone)))) {
                        (void)fprintf(stderr,
                                      "%s: no space for clone device: ", Pn);
                        safestrprt(fp, stderr, 1);
                        Error(ctx);
                    }
                    c->dx = i;
                    c->next = Clone;
                    Clone = c;
                }
#    endif /* defined(HAS_STD_CLONE) && HAS_STD_CLONE==1 */

                i++;
            }

#    if defined(HASBLKDEV)
            if ((sb.st_mode & S_IFMT) == S_IFBLK) {

                /*
                 * Save block device information in BDevtp[].
                 */
                if (j >= BNdev) {
                    BNdev += DEVINCR;
                    if (!BDevtp)
                        BDevtp = (struct l_dev *)malloc(
                            (MALLOC_S)(sizeof(struct l_dev) * BNdev));
                    else
                        BDevtp = (struct l_dev *)realloc(
                            (MALLOC_P *)BDevtp,
                            (MALLOC_S)(sizeof(struct l_dev) * BNdev));
                    if (!BDevtp) {
                        (void)fprintf(stderr, "%s: no space for block device\n",
                                      Pn);
                        Error(ctx);
                    }
                }
                BDevtp[j].name = fp;
                fp = (char *)NULL;
                BDevtp[j].inode = (INODETYPE)sb.st_ino;
                BDevtp[j].rdev = RDEV_EXPDEV(sb.st_rdev);
                BDevtp[j].v = 0;
                j++;
            }
#    endif /* defined(HASBLKDEV) */
        }
        (void)CloseDir(dfp);
    }
    /*
     * Free any allocated space.
     */
    if (!Dstk) {
        (void)free((FREE_P *)Dstk);
        Dstk = (char **)NULL;
    }
    if (fp)
        (void)free((FREE_P *)fp);
    if (path)
        (void)free((FREE_P *)path);

#    if defined(HASBLKDEV)
    /*
     * Reduce the BDevtp[] (optional) and Devtp[] tables to their minimum
     * sizes; allocate and build sort pointer lists; and sort the tables by
     * device number.
     */
    if (BNdev) {
        if (BNdev > j) {
            BNdev = j;
            BDevtp = (struct l_dev *)realloc(
                (MALLOC_P *)BDevtp, (MALLOC_S)(sizeof(struct l_dev) * BNdev));
        }
        if (!(BSdev = (struct l_dev **)malloc(
                  (MALLOC_S)(sizeof(struct l_dev *) * BNdev)))) {
            (void)fprintf(stderr,
                          "%s: no space for block device sort pointers\n", Pn);
            Error(ctx);
        }
        for (j = 0; j < BNdev; j++) {
            BSdev[j] = &BDevtp[j];
        }
        (void)qsort((QSORT_P *)BSdev, (size_t)BNdev,
                    (size_t)sizeof(struct l_dev *), compdev);
        BNdev = rmdupdev(ctx, &BSdev, BNdev, "block");
    }

#        if !defined(NOWARNBLKDEV)
    else {
        if (!Fwarn)
            (void)fprintf(stderr, "%s: WARNING: no block devices found\n", Pn);
    }
#        endif /* !defined(NOWARNBLKDEV) */
#    endif     /* defined(HASBLKDEV) */

    if (Ndev) {
        if (Ndev > i) {
            Ndev = i;
            Devtp = (struct l_dev *)realloc(
                (MALLOC_P *)Devtp, (MALLOC_S)(sizeof(struct l_dev) * Ndev));
        }
        if (!(Sdev = (struct l_dev **)malloc(
                  (MALLOC_S)(sizeof(struct l_dev *) * Ndev)))) {
            (void)fprintf(stderr,
                          "%s: no space for character device sort pointers\n",
                          Pn);
            Error(ctx);
        }
        for (i = 0; i < Ndev; i++) {
            Sdev[i] = &Devtp[i];
        }
        (void)qsort((QSORT_P *)Sdev, (size_t)Ndev,
                    (size_t)sizeof(struct l_dev *), compdev);
        Ndev = rmdupdev(ctx, &Sdev, Ndev, "char");
    } else {
        (void)fprintf(stderr, "%s: no character devices found\n", Pn);
        Error(ctx);
    }

#    if defined(HASDCACHE)
    /*
     * Write device cache file, as required.
     */
    if (DCstate == 1 || (DCstate == 3 && dcrd))
        write_dcache(ctx);
#    endif /* defined(HASDCACHE) */
}

#    if defined(HASDCACHE)
/*
 * rereaddev() - reread device names, modes and types
 */

void rereaddev(struct lsof_context *ctx) {
    (void)clr_devtab(ctx);

#        if defined(DCACHE_CLR)
    (void)DCACHE_CLR();
#        endif /* defined(DCACHE_CLR) */

    readdev(ctx, 1);
    DCunsafe = 0;
}
#    endif /* defined(HASDCACHE) */

/*
 * rmdupdev() - remove duplicate (major/minor/inode) devices
 */

static int rmdupdev(struct lsof_context *ctx,
                    struct l_dev ***dp, /* device table pointers address */
                    int n,              /* number of pointers */
                    char *nm) /* device table name for error message */
{

#    if defined(HAS_STD_CLONE) && HAS_STD_CLONE == 1
    struct clone *c, *cp;
#    endif /* defined(HAS_STD_CLONE) && HAS_STD_CLONE==1 */

    int i, j, k;
    struct l_dev **p;

    for (i = j = 0, p = *dp; i < n;) {
        for (k = i + 1; k < n; k++) {
            if (p[i]->rdev != p[k]->rdev || p[i]->inode != p[k]->inode)
                break;

#    if defined(HAS_STD_CLONE) && HAS_STD_CLONE == 1
            /*
             * See if we're deleting a duplicate clone device.  If so,
             * delete its clone table entry.
             */
            for (c = Clone, cp = (struct clone *)NULL; c; cp = c, c = c->next) {
                if (&Devtp[c->dx] != p[k])
                    continue;
                if (!cp)
                    Clone = c->next;
                else
                    cp->next = c->next;
                (void)free((FREE_P *)c);
                break;
            }
#    endif /* defined(HAS_STD_CLONE) && HAS_STD_CLONE==1 */
        }
        if (i != j)
            p[j] = p[i];
        j++;
        i = k;
    }
    if (n == j)
        return (n);
    if (!(*dp = (struct l_dev **)realloc(
              (MALLOC_P *)*dp, (MALLOC_S)(j * sizeof(struct l_dev *))))) {
        (void)fprintf(stderr, "%s: can't realloc %s device pointers\n", Pn, nm);
        Error(ctx);
    }
    return (j);
}

#    if defined(HASDCACHE)
/*
 * vfy_dev() - verify a device table entry (usually when DCunsafe == 1)
 *
 * Note: rereads entire device table when an entry can't be verified.
 */

int vfy_dev(struct lsof_context *ctx,
            struct l_dev *dp) /* device table pointer */
{
    struct stat sb;

    if (!DCunsafe || dp->v)
        return (1);
    if (RDEV_STATFN(dp->name, &sb) != 0 ||
        dp->rdev != RDEV_EXPDEV(sb.st_rdev) || dp->inode != sb.st_ino) {
        (void)rereaddev(ctx);
        return (0);
    }
    dp->v = 1;
    return (1);
}
#    endif /* defined(HASDCACHE) */
#else      /* !defined(USE_LIB_READDEV) */
char rdev_d1[] = "d";
char *rdev_d2 = rdev_d1;
#endif     /* defined(USE_LIB_READDEV) */
