/*
 * LTbasic.c -- Lsof Test basic tests
 *
 * The basic tests measure the finding by lsof of its own open CWD, open
 * executable (when possible), and open /dev/kmem files.
 *
 * 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.
 */

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

#include "LsofTest.h"
#include "lsof_fields.h"


/*
 * Local definitions
 */


/*
 * Globals
 */

char *Pn = (char *)NULL;	/* program name */


/*
 * Local function prototypes
 */

_PROTOTYPE(static void cleanup,(void));
_PROTOTYPE(static char *tstlsof,(char **texec, char **tkmem, char **tproc));


/*
 * Main program for dialects that support locking tests.
 */

int
main(argc, argv)
    int argc;				/* argument count */
    char *argv[];			/* arguments */
{
    char buf[2048];			/* temporary buffer */
    char *em;				/* error message pointer */
    char *texec = (char *)NULL;		/* lsof executable test result */
    char *tkmem = (char *)NULL;		/* /dev/kmem test result */
    char *tproc = (char *)NULL;		/* lsof process 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];
    (void) printf("%s ... ", Pn);
    (void) fflush(stdout);
    PrtMsg((char *)NULL, Pn);
/*
 * Process arguments.
 */
    if (ScanArg(argc, argv, "h", Pn))
	xv = 1;
    if (xv || LTopt_h) {
	(void) PrtMsg("usage: [-h]", Pn);
	PrtMsgX      ("       -h       print help (this panel)", Pn, cleanup,
		      xv);
    }
/*
 * 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);
/*
 * Test lsof.
 */
    if ((em = tstlsof(&texec, &tkmem, &tproc)))
	PrtMsg(em, Pn);
    if (texec)
	PrtMsg(texec, Pn);
    if (tkmem)
	PrtMsg(tkmem, Pn);
    if (tproc)
	PrtMsg(tproc, Pn);
/*
 * Compute exit value and exit.
 */
    if (em || texec || tkmem || tproc) {
	if (strcmp(LT_DEF_LSOF_PATH, LsofPath)) {
	    PrtMsg (" ", Pn);
	    PrtMsg ("Hint: you used the LT_LSOF_PATH environment variable to",
		Pn);
	    PrtMsg ("  specify this path to the lsof executable:\n", Pn);
	    (void) snprintf(buf, sizeof(buf) - 1, "      %s\n", LsofPath);
	    buf[sizeof(buf) - 1] = '\0';
	    PrtMsg (buf, Pn);
	    PrtMsgX("  Make sure its revision is 4.63 or higher.",
		Pn, cleanup, 1);
	} else
	    PrtMsgX("", Pn, cleanup, 1);
    }
    (void) PrtMsgX("OK", Pn, cleanup, 0);
    return(0);
}


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

static void
cleanup()
{
}


/*
 * tstlsof() -- test for the lsof process
 */

static char *
tstlsof(texec, tkmem, tproc)
    char **texec;			/* result of the executable test */
    char **tkmem;			/* result of the /dev/kmem test */
    char **tproc;			/* result of the lsof process test */
{
    char buf[2048];			/* temporary buffer */
    char *cem;				/* current error message pointer */
    LTfldo_t *cmdp;			/* command pointer */
    LTdev_t cwddc;			/* CWD device components */
    struct stat cwdsb;			/* CWD stat(2) buffer */
    LTfldo_t *devp;			/* device pointer */
    int execs = 0;			/* executable status */
    int fdn;				/* FD is a number */
    LTfldo_t *fdp;			/* file descriptor pointer */
    LTfldo_t *fop;			/* field output pointer */
    char ibuf[64];			/* inode string buffer */
    LTfldo_t *inop;			/* inode number pointer */
    LTdev_t kmemdc;			/* /dev/kmem device components */
    int kmems = 0;			/* kmem status */
    struct stat kmemsb;			/* /dev/kmem stat(2) buffer */
    LTdev_t lsofdc;			/* lsof device components */
    struct stat lsofsb;			/* lsof stat(2) buffer */
    int nf;				/* number of fields */
    char *opv[4];			/* option vector for ExecLsof() */
    char *pem = (char *)NULL;		/* previous error message */
    pid_t pid;				/* PID */
    int pids = 0;			/* PID found status */
    int procs = 0;			/* process status */
    LTfldo_t *rdevp;			/* raw device pointer */
    char *tcp;				/* temporary character pointer */
    int ti;				/* temporary integer */
    LTdev_t tmpdc;			/* temporary device components */
    LTfldo_t *typ;			/* file type pointer */
    int xwhile;				/* exit while() flag */

/*
 * Get lsof executable's stat(2) information.
 */
    if (stat(LsofPath, &lsofsb)) {
	(void) snprintf(buf, sizeof(buf) - 1, "ERROR!!!  stat(%s): %s",
	    LsofPath, strerror(errno));
	buf[sizeof(buf) - 1] = '\0';
	cem = MkStrCpy(buf, &ti);
	if (pem)
	    (void) PrtMsg(pem, Pn);
	pem = cem;
	execs = 1;
    } else if ((cem = ConvStatDev(&lsofsb.st_dev, &lsofdc))) {
	if (pem)
	    (void) PrtMsg(pem, Pn);
	pem = cem;
	execs = 1;
    }

#if	defined(LT_KMEM)
/*
 * Get /dev/kmem's stat(2) information.
 */
    if (stat("/dev/kmem", &kmemsb)) {
	(void) snprintf(buf, sizeof(buf) - 1,
	    "ERROR!!!  can't stat(2) /dev/kmem: %s", strerror(errno));
	buf[sizeof(buf) - 1] = '\0';
	cem = MkStrCpy(buf, &ti);
	if (pem)
	    (void) PrtMsg(pem, Pn);
	pem = cem;
	kmems = 1;
    } else if ((cem = ConvStatDev(&kmemsb.st_rdev, &kmemdc))) {
	if (pem)
	    (void) PrtMsg(pem, Pn);
	pem = cem;
	kmems = 1;
    }
#else	/* !defined(LT_KMEM) */
    kmems = 1;
#endif	/* defined(LT_KMEM) */

/*
 * Get CWD's stat(2) information.
 */
    if (stat(".", &cwdsb)) {
	(void) snprintf(buf, sizeof(buf) - 1, "ERROR!!!  stat(.): %s",
	    strerror(errno));
	buf[sizeof(buf) - 1] = '\0';
	cem = MkStrCpy(buf, &ti);
	if (pem)
	    (void) PrtMsg(pem, Pn);
	pem = cem;
	procs = 1;
    } else if ((cem = ConvStatDev(&cwdsb.st_dev, &cwddc))) {
	if (pem)
	    (void) PrtMsg(pem, Pn);
	pem = cem;
	procs = 1;
    }

/*
 * Complete the option vector and start lsof execution.
 */
    ti = 0;

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

#if	defined(USE_LSOF_X_OPT)
    opv[ti++] = "-X";
#endif	/* defined(USE_LSOF_X_OPT) */

    opv[ti++] = "-clsof";
    opv[ti] = (char *)NULL;
    if ((cem = ExecLsof(opv))) {
	if (pem)
	    (void) PrtMsg(pem, Pn);
	return(cem);
    }
/*
 * Read lsof output.
 */
    xwhile = execs + kmems + procs;
    while ((xwhile < 3) && (fop = RdFrLsof(&nf, &cem))) {
	if (pem)
	    (void) PrtMsg(pem, Pn);
	pem = cem;
	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 != LsofPid))
		pids = 0;
	    break;
	case LSOF_FID_FD:

	/*
	 * This is a file descriptor line.  Scan its fields.
	 */
	    if (!pids)
		break;
	    devp = inop = rdevp = typ = (LTfldo_t *)NULL;
	    fdp = fop;
	    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_RDEV:
		    rdevp = fop;
		    break;
		case LSOF_FID_TYPE:
		    typ = fop;
		    break;
		}
	    }
	/*
	 * A file descriptor line has been processes.
	 *
	 * Set the descriptor's numeric status.
	 *
	 * Check descriptor by FD type.
	 */

	    for (fdn = 0, tcp = fdp->v; *tcp; tcp++) {
		if (!isdigit((unsigned char)*tcp)) {
		    fdn = -1;
		    break;
		}
		fdn = (fdn * 10) + (int)(*tcp - '0');
	    }
	    if (!procs
	    &&  (fdn == -1)
	    &&  !strcasecmp(fdp->v, "cwd")
	    &&  typ
	    &&  (!strcasecmp(typ->v, "DIR") || !strcasecmp(typ->v, "VDIR"))
	    ) {

	    /*
	     * This is the CWD for the process.  Make sure its information
	     * matches what stat(2) said about the CWD.
	     */
		if (!devp || !inop)
		    break;
		if ((cem = ConvLsofDev(devp->v, &tmpdc))) {
		    if (pem)
			(void) PrtMsg(pem, Pn);
		    pem = cem;
		    break;
		}
		(void) snprintf(ibuf, sizeof(ibuf) - 1, "%" PRIu64,
		    (uint64_t)cwdsb.st_ino);
		ibuf[sizeof(ibuf) - 1] = '\0';
		if ((tmpdc.maj == cwddc.maj)
		&&  (tmpdc.min == cwddc.min)
		&&  (tmpdc.unit == cwddc.unit)
		&&  !strcmp(inop->v, ibuf)
		) {
		    procs = 1;
		    xwhile++;
		}
		break;
	    }
	    if (!kmems
	    &&  (fdn >= 0)
	    &&  typ
	    &&  (!strcasecmp(typ->v, "CHR") || !strcasecmp(typ->v, "VCHR"))
	    ) {

	    /*
	     * /dev/kmem hasn't been found and this is an open character device
	     * file with a numeric descriptor.
	     *
	     * See if it is /dev/kmem.
	     */
		if (!inop || !rdevp)
		    break;
		if ((cem = ConvLsofDev(rdevp->v, &tmpdc))) {
		    if (pem)
			(void) PrtMsg(pem, Pn);
		    pem = cem;
		    break;
		}
		(void) snprintf(ibuf, sizeof(ibuf) - 1, "%" PRIu64,
		    (uint64_t)kmemsb.st_ino);
		ibuf[sizeof(ibuf) - 1] = '\0';
		if ((tmpdc.maj == kmemdc.maj)
		&&  (tmpdc.min == kmemdc.min)
		&&  (tmpdc.unit == kmemdc.unit)
		&&  !strcmp(inop->v, ibuf)
		) {
		    kmems = 1;
		    xwhile++;
		}
		break;
	    }
	    if (!execs
	    &&  (fdn == -1)
	    &&  typ
	    &&  (!strcasecmp(typ->v, "REG") || !strcasecmp(typ->v, "VREG"))
	    ) {

	    /*
	     * If this is a regular file with a non-numeric FD, it may be the
	     * executable.
	     */
		if (!devp || !inop)
		    break;
	        if ((cem = ConvLsofDev(devp->v, &lsofdc))) {
		    if (pem)
			(void) PrtMsg(pem, Pn);
		    pem = cem;
		    break;
		}
		(void) snprintf(ibuf, sizeof(ibuf) - 1, "%" PRIu64,
		    (uint64_t)lsofsb.st_ino);
		ibuf[sizeof(ibuf) - 1] = '\0';
		if ((tmpdc.maj == lsofdc.maj)
		&&  (tmpdc.min == lsofdc.min)
		&&  (tmpdc.unit == lsofdc.unit)
		&&  !strcmp(inop->v, ibuf)
		) {
		    execs = 1;
		    xwhile++;
		}
	    }
	}
    }
    (void) StopLsof();
    if (!execs)
	*texec = "ERROR!!!  open lsof executable wasn't found.";
    if (!kmems)
	*tkmem = "ERROR!!!  open lsof /dev/kmem usage wasn't found.";
    if (!procs)
	*tproc = "ERROR!!!  lsof process wasn't found.";
    return(pem);
}
