/*
 * LTbigf.c -- Lsof Test big file size and offset tests
 *
 * V. Abell
 * Purdue University
 */

/*
 * Copyright 2002 Purdue Research Foundation, West Lafayette, Indiana
 * 47907.  All rights reserved.
 *
 * Written by V. 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 "LsofTest.h"

#if !defined(LT_BIGF)

/*
 * Here begins the version of this program for dialects that don't support
 * large files.
 */

/*
 * Main program for dialects that don't support large files
 */

int main(int argc,     /* argument count */
         char *argv[]) /* arguments */
{
    char *pn; /* program name */
              /*
               * Get program name and issue start and exit message.
               */
    if ((pn = (char *)strrchr(argv[0], '/')))
        pn++;
    else
        pn = argv[0];

    (void)printf("%s ... %s\n", pn, LT_DONT_DO_TEST);
    return (0);
}
#else /* defined(LT_BIGF) */

/*
 * Here begins the version of this program for dialects that support
 * large files.
 */

#    include "lsof_fields.h"

/*
 * Pre-definitions that may be changed by specific dialects
 */

#    define OFFTST_STAT 1 /* offset tests status */

#    if defined(LT_DIAL_aix)
/*
 * AIX-specific definitions
 */

#        define OFFSET_T off64_t /* define offset type */
#    endif                       /* defined(LT_DIAL_aix) */

#    if defined(LT_DIAL_bsdi)
/*
 * BSDI-specific definitions
 */

#        define OFFSET_T off_t    /* define offset type */
#        define OPENF open        /* define open function */
#        define SEEKF lseek       /* define seek function */
#        define STATF stat        /* define stat function */
#        define STATS struct stat /* define stat structure */
#    endif                        /* defined(LT_DIAL_bsdi) */

#    if defined(LT_DIAL_darwin)
/*
 * Darwin-specific definitions
 */

#        if LT_VERS >= 900
#            define OFFSET_T off_t    /* define offset type */
#            define OPENF open        /* define open function */
#            define SEEKF lseek       /* define seek function */
#            define STATF stat        /* define stat function */
#            define STATS struct stat /* define stat structure */
#        endif                        /* LT_VERS>=900 */
#    endif                            /* defined(LT_DIAL_darwin) */

#    if defined(LT_DIAL_du)
/*
 * DEC_OSF/1|Digital_UNIX|Tru64_UNIX-specific items
 */

#        define OFFSET_T off_t    /* define offset type */
#        define OPENF open        /* define open function */
#        define SEEKF lseek       /* define seek function */
#        define STATF stat        /* define stat function */
#        define STATS struct stat /* define stat structure */
#    endif                        /* defined(LT_DIAL_du) */

#    if defined(LT_DIAL_freebsd)
/*
 * FreeBSD-specific definitions
 */

#        define OFFSET_T off_t    /* define offset type */
#        define OPENF open        /* define open function */
#        define SEEKF lseek       /* define seek function */
#        define STATF stat        /* define stat function */
#        define STATS struct stat /* define stat structure */
#    endif                        /* defined(LT_DIAL_freebsd) */

#    if defined(LT_DIAL_linux)
/*
 * Linux-specific definitions
 */

#        undef OFFTST_STAT
#        define OFFTST_STAT                                                    \
            0                     /* Linux lsof may not be able to report      \
                                   * offsets -- see the function               \
                                   * ck_Linux_offset_support() */
#        define OFFSET_T off_t    /* define offset type */
#        define OPENF open        /* define open function */
#        define SEEKF lseek       /* define seek function */
#        define STATF stat        /* define stat function */
#        define STATS struct stat /* define stat structure */

static int ck_Linux_offset_support(void);
#    endif                        /* defined(LT_DIAL_linux) */

#    if defined(LT_DIAL_hpux)
/*
 * HP-UX-specific definitions
 */

#        define OFFSET_T off64_t /* define offset type */
#    endif                       /* defined(LT_DIAL_hpux) */

#    if defined(LT_DIAL_netbsd)
/*
 * NetBSD-specific definitions
 */

#        define OFFSET_T off_t    /* define offset type */
#        define OPENF open        /* define open function */
#        define SEEKF lseek       /* define seek function */
#        define STATF stat        /* define stat function */
#        define STATS struct stat /* define stat structure */
#    endif                        /* defined(LT_DIAL_netbsd) */

#    if defined(LT_DIAL_openbsd)
/*
 * OpenBSD-specific definitions
 */

#        define OFFSET_T off_t    /* define offset type */
#        define OPENF open        /* define open function */
#        define SEEKF lseek       /* define seek function */
#        define STATF stat        /* define stat function */
#        define STATS struct stat /* define stat structure */
#    endif                        /* defined(LT_DIAL_openbsd) */

#    if defined(LT_DIAL_ou)
/*
 * OpenUNIX-specific items
 */

#        include <signal.h>

#        define IGNORE_SIGXFSZ
#        define OFFSET_T off64_t /* define offset type */
#    endif                       /* defined(LT_DIAL_ou) */

#    if defined(LT_DIAL_solaris)
/*
 * Solaris-specific definitions
 */

#        define OFFSET_T off64_t /* define offset type */
#    endif                       /* defined(LT_DIAL_solaris) */

#    if defined(LT_DIAL_uw)
/*
 * UnixWare-specific items
 */

#        include <signal.h>

#        define IGNORE_SIGXFSZ
#        define OFFSET_T off64_t /* define offset type */
#    endif                       /* defined(LT_DIAL_uw) */

/*
 * Local definitions
 */

#    if !defined(OPENF)
#        define OPENF open64 /* open() function */
#    endif                   /* !defined(OPENF) */

#    if !defined(OFFSET_T)
#        define OFFSET_T unsigned long long /* offset type */
#    endif                                  /* !defined(OFFSET_T) */

#    if !defined(SEEKF)
#        define SEEKF lseek64 /* seek() function */
#    endif                    /* !defined(SEEKF) */

#    if !defined(STATF)
#        define STATF stat64 /* stat(2) structure */
#    endif                   /* !defined(STATF) */

#    if !defined(STATS)
#        define STATS struct stat64 /* stat(2) structure */
#    endif                          /* !defined(STATS) */

#    define TST_OFFT 0 /* test offset in 0t decimal*/
#    define TST_OFFX 1 /* test offset in hex */
#    define TST_SZ 2   /* test size */

/*
 * Globals
 */

int Fd = -1;               /* test file descriptor; open if >= 0 */
pid_t MyPid = (pid_t)0;    /* PID of this process */
char *Path = (char *)NULL; /* test file path; none if NULL */
char *Pn = (char *)NULL;   /* program name */

/*
 * Local function prototypes
 */

static void cleanup(void);
static int tstwlsof(int tt, char *opt, OFFSET_T sz);

/*
 * Main program for dialects that support large files
 */

int main(int argc,     /* argument count */
         char *argv[]) /* arguments */
{
    char buf[2048];              /* temporary buffer */
    int do_offt = OFFTST_STAT;   /* do offset tests if == 1 */
    char *em;                    /* error message pointer */
    int i;                       /* temporary integer */
    int len;                     /* string length */
    OFFSET_T sz = 0x140000000ll; /* test file size */
    char szbuf[64];              /* size buffer */
    char *tcp;                   /* temporary character pointer */
    int tofft = 0;               /* 0t offset test result */
    int toffx = 0;               /* 0x offset test result */
    int tsz = 0;                 /* size test result */
    int xv = 0;                  /* exit value */
                                 /*
                                  * Get program name and PID, issue start message, and build space prefix.
                                  */
    if ((Pn = strrchr(argv[0], '/')))
        Pn++;
    else
        Pn = argv[0];
    MyPid = getpid();
    (void)printf("%s ... ", Pn);
    (void)fflush(stdout);
    PrtMsg((char *)NULL, Pn);
    /*
     * Process arguments.
     */
    if (ScanArg(argc, argv, "hp:", Pn))
        xv = 1;
    if (xv || LTopt_h) {
        (void)PrtMsg("usage: [-h] [-p path]", Pn);
        PrtMsg("       -h       print help (this panel)", Pn);
        PrtMsgX("       -p path  define test file path", Pn, cleanup, xv);
    }

#    if defined(LT_DIAL_linux)
    /*
     * If this is Linux, see if lsof can report file offsets.
     */
    do_offt = ck_Linux_offset_support();
#    endif /* defined(LT_DIAL_linux) */

    /*
     * See if lsof can be executed and can access kernel memory.
     */
    if ((em = IsLsofExec()))
        (void)PrtMsgX(em, Pn, cleanup, 1);
    if ((em = CanRdKmem()))
        (void)PrtMsgX(em, Pn, cleanup, 1);
    /*
     * Construct the path.  If LT_BIGSZOFF_PATH is defined in the environment,
     * use it. otherwise construct a path in the CWD.
     */
    if (!(Path = LTopt_p)) {
        (void)snprintf(buf, sizeof(buf), "./config.LTbigf%ld", (long)MyPid);
        buf[sizeof(buf) - 1] = '\0';
        Path = MkStrCpy(buf, &len);
    }
    /*
     * Fill buffer for writing to the test file.
     */
    for (i = 0; i < sizeof(buf); i++) {
        buf[i] = (char)(i & 0xff);
    }

#    if defined(IGNORE_SIGXFSZ)
    /*
     * Ignore SIGXFSZ, if directed by a dialect-specific option.
     */
    (void)signal(SIGXFSZ, SIG_IGN);
#    endif /* defined(IGNORE_SIGXFSZ) */

    /*
     * Open a new test file at the specified path.
     */
    (void)unlink(Path);
    if ((Fd = OPENF(Path, O_RDWR | O_CREAT, 0600)) < 0) {
        (void)fprintf(stderr, "ERROR!!!  can't open %s\n", Path);

    print_hint:

        /*
         * Print a hint about the LT_BIGSZOFF_PATH environment variable.
         */

        MsgStat = 1;
        (void)snprintf(buf, sizeof(buf) - 1, "      Errno %d: %s", errno,
                       strerror(errno));
        buf[sizeof(buf) - 1] = '\0';
        (void)PrtMsg(buf, Pn);
        (void)PrtMsg("Hint: try using \"-p path\" to supply a path in a", Pn);
        (void)PrtMsg("file system that has large file support enabled.\n", Pn);
        (void)PrtMsg("Hint: try raising the process ulimit file block", Pn);
        (void)PrtMsg("size to a value that will permit this test to", Pn);
        (void)snprintf(szbuf, sizeof(szbuf) - 1, "%lld", (long long)sz);
        szbuf[sizeof(szbuf) - 1] = '\0';
        (void)snprintf(buf, sizeof(buf) - 1,
                       "write a file whose size appears to be %s", szbuf);
        buf[sizeof(buf) - 1] = '\0';
        (void)PrtMsg(buf, Pn);
        (void)PrtMsg("bytes.  (The file really isn't that big -- it", Pn);
        (void)PrtMsg("just has a large \"hole\" in its mid-section.)\n", Pn);
        (void)PrtMsgX("See 00FAQ and 00TEST for more information.", Pn, cleanup,
                      1);
    }
    /*
     * Write a buffer load at the beginning of the file.
     */
    if (SEEKF(Fd, (OFFSET_T)0, SEEK_SET) < 0) {
        (void)fprintf(stderr, "ERROR!!!  can't seek to the beginning of %s\n",
                      Path);
        goto print_hint;
    }
    if (write(Fd, buf, sizeof(buf)) != sizeof(buf)) {
        (void)fprintf(stderr,
                      "ERROR!!!  can't write %d bytes to the beginning of %s\n",
                      (int)sizeof(buf), Path);
        goto print_hint;
    }
    /*
     * Write a buffer load near the end of the file to bring it to the
     * specified length.  Leave the file open so lsof can find it.
     */
    if (SEEKF(Fd, (OFFSET_T)(sz - sizeof(buf)), SEEK_SET) < 0) {
        (void)snprintf(szbuf, sizeof(szbuf) - 1, "%lld",
                       (unsigned long long)(sz - sizeof(buf)));
        (void)fprintf(stderr, "ERROR!!!  can't seek to %s in %s\n", szbuf,
                      Path);
        goto print_hint;
    }
    if (write(Fd, buf, sizeof(buf)) != sizeof(buf)) {
        (void)fprintf(stderr,
                      "ERROR!!!  can't write %d bytes near the end of %s\n",
                      (int)sizeof(buf), Path);
        goto print_hint;
    }
    /*
     * Fsync() the file.
     */
    if (fsync(Fd)) {
        (void)fprintf(stderr, "ERROR!!!  can't fsync %s\n", Path);
        goto print_hint;
    }

    /*
     * If this dialect can't report offsets, disable the offset tests.
     */
    if (!do_offt) {
        tofft = toffx = 1;
        PrtMsg("WARNING!!!  lsof can't return file offsets for this dialect,",
               Pn);
        PrtMsg("  so offset tests have been disabled.", Pn);
    }
    /*
     * Do file size test.
     */
    tsz = tstwlsof(TST_SZ, "-s", sz);
    /*
     * If enabled, do offset tests.
     */
    if (!tofft)
        tofft = tstwlsof(TST_OFFT, "-oo20", sz);
    if (!toffx)
        toffx = tstwlsof(TST_OFFX, "-oo2", sz);
    /*
     * Compute exit value and exit.
     */
    if ((tsz != 1) || (tofft != 1) || (toffx != 1)) {
        tcp = (char *)NULL;
        xv = 1;
    } else {
        tcp = "OK";
        xv = 0;
    }
    (void)PrtMsgX(tcp, Pn, cleanup, xv);
    return (0);
}

#    if defined(LT_DIAL_linux)
/*
 * ck_Linux_offset_support() -- see if lsof can report offsets for this
 *				Linux implementation
 */

static int ck_Linux_offset_support() {
    char buf[1024];         /* lsof output line buffer */
    int bufl = sizeof(buf); /* size of buf[] */
    char *opv[5];           /* option vector for lsof */
    int rv = 1;             /* return value:
                             *     0 == no lsof offset support
                             *     1 == lsof offset support */
                            /*
                             * Ask lsof to report the test's FD zero offset.
                             */
    if (IsLsofExec())
        return (0);
    opv[0] = "-o";
    snprintf(buf, bufl - 1, "-p%d", (int)getpid());
    opv[1] = buf;
    opv[2] = "-ad0";
    opv[3] = "+w";
    opv[4] = (char *)NULL;
    if (ExecLsof(opv))
        return (0);
    /*
     * Read the lsof output.  Look for a line with "WARNING: can't report
     * offset" in it.  If it is found, then this Linux lsof can't report
     * offsets.
     */
    while (fgets(buf, bufl - 1, LsofFs)) {
        if (strstr(buf, "WARNING: can't report offset")) {
            rv = 0;
            break;
        }
    }
    (void)StopLsof();
    return (rv);
}
#    endif /* defined(LT_DIAL_linux) */

/*
 * cleanup() -- release resources
 */

static void cleanup() {
    if (Fd >= 0) {
        /*
         * Close the test file.
         *
         * But first unlink it to discourage some kernel file system
         * implementations (e.g., HFS on Apple Darwin, aka Mac OS X) from trying
         * to fill the file's large holes.  (Filling can take a long time.)
         */
        if (Path) {
            (void)unlink(Path);
            Path = (char *)NULL;
        }
        (void)close(Fd);
        Fd = -1;
    }
}

/*
 * tstwlsof() -- test the open file with lsof
 */

static int tstwlsof(int tt,      /* test type -- i.e., TST_* */
                    char *opt,   /* additional lsof options */
                    OFFSET_T sz) /* expected size (and offset) */
{
    char buf[2048], buf1[2048]; /* temporary buffers */
    LTfldo_t *cmdp;             /* command pointer */
    LTfldo_t *devp;             /* device pointer */
    char *em;                   /* error message pointer */
    int ff = 0;                 /* file found status */
    LTfldo_t *fop;              /* field output pointer */
    LTfldo_t *inop;             /* inode number pointer */
    LTdev_t lsofdc;             /* lsof device components */
    int nf;                     /* number of fields */
    LTfldo_t *nmp;              /* file name pointer */
    LTfldo_t *offp;             /* file offset pointer */
    char *opv[4];               /* option vector for ExecLsof() */
    pid_t pid;                  /* PID */
    int pids = 0;               /* PID found status */
    STATS sb;                   /* stat(2) buffer */
    LTdev_t stdc;               /* stat(2) device components */
    LTfldo_t *szp;              /* file size pointer */
    LTfldo_t *tfop;             /* temporary field output pointer */
    int ti;                     /* temporary index */
    LTfldo_t *typ;              /* file type pointer */
    int xv = 0;                 /* exit value */
                                /*
                                 * Check the test type.
                                 */
    switch (tt) {
    case TST_OFFT:
    case TST_OFFX:
    case TST_SZ:
        break;
    default:
        (void)snprintf(buf, sizeof(buf) - 1, "ERROR!!! unknown test type: %d",
                       tt);
        buf[sizeof(buf) - 1] = '\0';
        (void)PrtMsgX(buf, Pn, cleanup, 1);
    }
    /*
     * Get test file's information.
     */
    if (STATF(Path, &sb)) {
        (void)snprintf(buf, sizeof(buf) - 1, "ERROR!!! can't stat(2) %s: %s",
                       Path, strerror(errno));
        buf[sizeof(buf) - 1] = '\0';
        (void)PrtMsgX(buf, Pn, cleanup, 1);
    }
    /*
     * Extract components from test file's device number.
     */
    if ((em = ConvStatDev(&sb.st_dev, &stdc))) {
        (void)PrtMsg(em, Pn);
        return (0);
    }
    /*
     * Complete the option vector and start lsof execution.
     */
    ti = 0;
    if (opt && *opt)
        opv[ti++] = opt;

#    if defined(USE_LSOF_C_OPT)
    opv[ti++] = "-C";
#    else  /* !defined(USE_LSOF_C_OPT) */
    opv[ti++] = "--";
#    endif /* defined(USE_LSOF_C_OPT) */

    opv[ti++] = Path;
    opv[ti] = (char *)NULL;
    if ((em = ExecLsof(opv))) {
        (void)PrtMsg(em, Pn);
        return (0);
    }
    /*
     * Read lsof output.
     */
    while (!ff && (fop = RdFrLsof(&nf, &em))) {
        switch (fop->ft) {
        case LSOF_FID_PID:

            /*
             * This is a process information line.
             */
            pid = (pid_t)atoi(fop->v);
            pids = 1;
            cmdp = (LTfldo_t *)NULL;
            for (fop++, ti = 1; ti < nf; fop++, ti++) {
                switch (fop->ft) {
                case LSOF_FID_CMD:
                    cmdp = fop;
                    break;
                }
            }
            if (!cmdp || (pid != MyPid))
                pids = 0;
            break;
        case LSOF_FID_FD:

            /*
             * This is a file descriptor line.
             *
             * Scan for device number, inode number, name, offset, size, and
             * type fields.
             */
            if (!pids)
                break;
            devp = inop = nmp = offp = szp = typ = (LTfldo_t *)NULL;
            for (fop++, ti = 1; ti < nf; fop++, ti++) {
                switch (fop->ft) {
                case LSOF_FID_DEVN:
                    devp = fop;
                    break;
                case LSOF_FID_INODE:
                    inop = fop;
                    break;
                case LSOF_FID_NAME:
                    nmp = fop;
                    break;
                case LSOF_FID_OFFSET:
                    offp = fop;
                    break;
                case LSOF_FID_SIZE:
                    szp = fop;
                    break;
                case LSOF_FID_TYPE:
                    typ = fop;
                    break;
                }
            }
            /*
             * Check the results of the file descriptor field scan.
             *
             * (Don't compare path names because of symbolic link interference.)
             */
            if (!devp || !inop || !nmp || !typ)
                break;
            if (strcasecmp(typ->v, "reg") && strcasecmp(typ->v, "vreg"))
                break;
            if (ConvLsofDev(devp->v, &lsofdc))
                break;
            if ((stdc.maj != lsofdc.maj) || (stdc.min != lsofdc.min) ||
                (stdc.unit != lsofdc.unit))
                break;
            (void)snprintf(buf, sizeof(buf) - 1, "%llu",
                           (unsigned long long)sb.st_ino);
            buf[sizeof(buf) - 1] = '\0';
            if (strcmp(inop->v, buf))
                break;
            /*
             * The specifed file has been located.  Check its size or offset,
             * according to the tt argument.
             */
            ff = 1;
            switch (tt) {
            case TST_OFFT:
            case TST_SZ:

                /*
                 * Test the size as an offset in decimal with a leading "0t", or
                 * test the size as a size in decimal.
                 */
                (void)snprintf(buf, sizeof(buf) - 1,
                               (tt == TST_SZ) ? "%llu" : "0t%llu",
                               (unsigned long long)sz);
                buf[sizeof(buf) - 1] = '\0';
                tfop = (tt == TST_SZ) ? szp : offp;
                if (!tfop || strcmp(tfop->v, buf)) {
                    (void)snprintf(buf1, sizeof(buf1) - 1,
                                   "%s mismatch: expected %s, got %s",
                                   (tt == TST_SZ) ? "size" : "0t offset", buf,
                                   tfop ? tfop->v : "nothing");
                    buf1[sizeof(buf1) - 1] = '\0';
                    (void)PrtMsg(buf1, Pn);
                    xv = 0;
                } else
                    xv = 1;
                break;
            case TST_OFFX:

                /*
                 * Test the size as an offset in hex.
                 */
                (void)snprintf(buf, sizeof(buf) - 1, "0x%llx",
                               (unsigned long long)sz);
                buf[sizeof(buf) - 1] = '\0';
                if (!offp || strcmp(offp->v, buf)) {
                    (void)snprintf(buf1, sizeof(buf1) - 1,
                                   "0x offset mismatch: expected %s, got %s",
                                   buf, offp ? offp->v : "nothing");
                    buf1[sizeof(buf1) - 1] = '\0';
                    (void)PrtMsg(buf1, Pn);
                    xv = 0;
                } else
                    xv = 1;
            }
            break;
        }
    }
    (void)StopLsof();
    if (em) {

        /*
         * RdFrLsof() encountered an error.
         */
        (void)PrtMsg(em, Pn);
        xv = 0;
    }
    if (!ff) {
        (void)snprintf(buf, sizeof(buf) - 1, "%s not found by lsof", Path);
        buf[sizeof(buf) - 1] = '\0';
        PrtMsg(buf, Pn);
        xv = 0;
    }
    return (xv);
}
#endif     /* defined(LT_BIG) */
