Blob Blame History Raw
/*
 * LTlib.c -- the lsof test library
 *
 * 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"


/*
 * Pre-defintions that may be changed by a specific dialect
 */

#define	X2DEV_T		unsigned int	/* cast for result of x2dev() */
#define	XDINDEV		8		/* number of hex digits in an lsof
					 * device field -- should be
					 * 2 X sizeof(X2DEV_T) */


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

#include <sys/sysmacros.h>

# if	defined(LT_AIXA) && LT_AIXA>=1

/*
 * Note: the DEVNO64 and ISDEVNO54 #define's come from <sys/sysmacros.h>, but
 * only when _KERNEL is #define'd.
 */

#undef	DEVNO64
#define	DEVNO64		0x8000000000000000LL
#undef	ISDEVNO64
#define	ISDEVNO64(d)	(((ulong)(d) & DEVNO64) ? 1 : 0)

/*
 * Define major and minor extraction macros that work on 64 bit AIX
 * architectures.
 */
 
#define	major_S(d)	(ISDEVNO64(d) ? major64(d) : minor(d & ~SDEV_REMOTE))
#define	minor_S(d)	(ISDEVNO64(d) ? (minor64(d) & ~SDEV_REMOTE) : minor(d))
#undef	X2DEV_T
#define	X2DEV_T		unsigned long long
#undef	XDINDEV
#define	XDINDEV		16
#define	major_X(dp, em)	major_S(x2dev(dp, em))
#define	minor_X(dp, em)	minor_S(x2dev(dp, em))
# endif	/* defined(LT_AIXA) && LT_AIXA>=1 */

#endif	/* defined(LT_DIAL_aix) */


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

#define	minor_S(dev)	dv_subunit(dev)
#define	unit_S(dev)	dv_unit(dev)
#define	minor_X(dp, em)	dv_subunit(x2dev(dp, em))
#define	unit_X(dp, em)	dv_unit(x2dev(dp, em))	
#endif	/* defined(LT_DIAL_bsdi) */


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

#undef	XDINDEV
#define	XDINDEV		16
# if	defined(LT_DEV64)
#undef	X2DEV_T
#define	X2DEV_T		unsigned long long
#define	major_X(dp, em)	((int)((x2dev(dp, em) >> 32) & 0xffffffff))
# endif	/* defined(LT_DEV64) */
#endif	/* defined(LT_DIAL_freebsd) */


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

#include <sys/sysmacros.h>
#endif	/* defined(LT_DIAL_osr) */


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

#include <sys/mkdev.h>
#endif	/* defined(LT_DIAL_ou) */


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

#include <sys/sysmacros.h>


/*
 * Define maximum major device number in a stat(2) dev_t
 */

# if	LT_VERS>=20501
#define LT_MJX	L_MAXMAJ	/* Get maximum major device number from
				 * <sys/sysmacros.h>. */
# else	/* LT_VERS<20501 */
#define	LT_MJX	0x3fff		/* Avoid <sys/sysmacros.h> when
				 * Solaris < 2.5.1. */
# endif /* LT_VERS>=20501 */

#define	major_S(dev)	((int)((dev >> L_BITSMINOR) & LT_MJX))
#define	minor_S(dev)	((int)(dev & L_MAXMIN))

# if	defined(LT_K64)

/*
 * Solaris 64 bit kernel
 */

#undef	X2DEV_T
#define	X2DEV_T		unsigned long long
#undef	XDINDEV
#define	XDINDEV		16

#define	major_X(dp, em)	((int)((x2dev(dp, em) >> 32) & 0xffffffff))
#define	minor_X(dp, em) ((int)(x2dev(dp, em) & 0xffffffff))
# else	/* !defined(LT_K64) */

/*
 * Solaris 32 bit kernel
 */

#define	major_X(dp, em)	((int)((x2dev(dp, em) >> L_BITSMINOR) & LT_MJX))
#define	minor_X(dp, em)	((int)(x2dev(dp, em) & L_MAXMIN))
# endif	/* LT_K64 */
#endif	/* defined(LT_DIAL_solaris) */


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

#include <sys/mkdev.h>
#endif	/* defined(LT_DIAL_uw) */


/*
 * Global variables
 */

int LsofFd = -1;			/* lsof pipe FD */
FILE *LsofFs = (FILE *)NULL;		/* stream for lsof pipe FD */
char *LsofPath = (char *)NULL;		/* path to lsof executable */
pid_t LsofPid = (pid_t)0;		/* PID of lsof child process */
int LTopt_h = 0;			/* "-h" option's switch value */
char *LTopt_p = (char *)NULL;		/* "-p path" option's path value */
int MsgStat = 0;			/* message status: 1 means prefix needs
					 * to be issued */


/*
 * Local static variables
 */

static int Afo = 0;			/* Fo[] structures allocated */
static char *GOv = (char *)NULL;	/* option `:' value pointer */
static int GOx1 = 1;			/* first opt[][] index */
static int GOx2 = 0;			/* second opt[][] index */
static LTfldo_t *Fo = (LTfldo_t *)NULL;	/* allocated LTfldo_t structures */
static int Ufo = 0;			/* Fo[] structures used */


/*
 * Local function prototypes
 */

_PROTOTYPE(static void closepipe,(void));
_PROTOTYPE(static void getlsofpath,(void));
_PROTOTYPE(static int GetOpt,(int ct, char *opt[], char *rules, char **em,
			 char *pn));
_PROTOTYPE(static X2DEV_T x2dev,(char *x, char **em));


/*
 * Default major, minor, and unit macros.
 */

#if	!defined(major_S)
#define	major_S		major
#endif	/* defined(major_S) */

#if	!defined(minor_S)
#define	minor_S		minor
#endif	/* defined(minor_S) */

#if	!defined(unit_S)
#define	unit_S(x)	0
#endif	/* defined(unit_S) */

#if	!defined(major_X)
#define	major_X(dp, em)	major(x2dev(dp, em))
#endif	/* defined(major_X) */

#if	!defined(minor_X)
#define	minor_X(dp, em)	minor(x2dev(dp, em))
#endif	/* defined(minor_X) */

#if	!defined(unit_X)
#define	unit_X(dp, em)	0
#endif	/* defined(unit_X) */


/*
 * CanRdKmem() -- can lsof read kernel memory devices?
 */

char *
CanRdKmem()
{

#if	defined(LT_KMEM)
    char buf[2048];			/* temporary buffer */
    char *dn;				/* memory device name */
    char *em;				/* error message pointer */
    int fd;				/* temporary file descriptor */
    struct stat sb;			/* memory device stat(2) buffer */
    int ti;				/* temporary integer */
/*
 * Get the lsof path.  If it is not the default, check no further.
 */
    (void) getlsofpath();
    if (!strcmp(LsofPath, LT_DEF_LSOF_PATH))
	return((char *)NULL);
/*
 * Check /dev/kmem access.
 */
    dn = "/dev/kmem";
    if (stat(dn, &sb)) {
	em = "stat";

kmem_error:

	(void) snprintf(buf, sizeof(buf) - 1,
	    "ERROR!!!  can't %s(%s): %s\n", em, dn, strerror(errno));
	buf[sizeof(buf) - 1] = '\0';
	return(MkStrCpy(buf, &ti));
    }
    if ((fd = open(dn, O_RDONLY, 0)) < 0) {
	em = "open";
	goto kmem_error;
    }
    (void) close(fd);
/*
 * Check /dev/mem access.
 */
    dn = "/dev/mem";
    if (stat(dn, &sb)) {

    /*
     * If /dev/mem can't be found, ignore the error.
     */
	return((char *)NULL);
    }
    if ((fd = open(dn, O_RDONLY, 0)) < 0) {
	em = "open";
	goto kmem_error;
    }
    (void) close(fd);
#endif	/* defined(LT_KMEM) */

    return((char *)NULL);
}


/*
 * closepipe() -- close pipe from lsof
 */

static void
closepipe()
{
    if (LsofFd >= 0) {

    /*
     * A pipe from lsof is open.  Close it and the associated stream.
     */
	if (LsofFs) {
	    (void) fclose(LsofFs);
	    LsofFs = (FILE *)NULL;
	}
	(void) close(LsofFd);
	LsofFd = -1;
    }
}


/*
 * ConvLsofDev() -- convert lsof device string
 *
 * Note: this function is dialect-specific.
 */

char *
ConvLsofDev(dev, ldev)
    char *dev;			/* lsof device string -- the value to the
				 * LSOF_FID_DEVN field of a LSOF_FID_FD block
				 * (see lsof_fields.h) */
    LTdev_t *ldev;		/* results are returned to this structure */
{
    char *dp;			/* device pointer */
    char *em;			/* error message pointer */
    int tlen;			/* temporary length */
/*
 * Check function arguments.
 *
 * Establish values for decoding the device string.
 */
    if (!dev)
	return("ERROR!!!  no ConvLsofDev() device");
    if (!ldev)
	return("ERROR!!!  no ConvLsofDev() result pointer");
    if (strncmp(dev, "0x", 2))
	return("ERROR!!!  no leading 0x in ConvLsofDev() device");
    dp = dev + 2;
    if (((tlen = (int)strlen(dp)) < 1) || (tlen > XDINDEV))
	return("ERROR!!!  bad ConvLsofDev() device length");
/*
 * Use the pre-defined *_X() macros to do the decomposition.
 */
    ldev->maj = (unsigned int)major_X(dp, &em);
    if (em)
	return(em);
    ldev->min = (unsigned int)minor_X(dp, &em);
    if (em)
	return(em);
    ldev->unit = (unsigned int)unit_X(dp, &em);
    return(em);
}


/*
 * ConvStatDev() -- convert stat(2) device number
 *
 * Note: this function is dialect-specific.
 */

char *
ConvStatDev(dev, ldev)
    dev_t *dev;			/* device number to be converted */
    LTdev_t *ldev;		/* results are returned to this structure */
{

/*
 * Check function arguments.
 */
    if (!dev)
	return("ERROR!!!  no ConvStatDev() device");
    if (!ldev)
	return("ERROR!!!  no ConvStatDev() result pointer");
/*
 * Use the pre-defined *_S() macros to do the decomposition.
 */
    ldev->maj = (unsigned int)major_S(*dev);    
    ldev->min = (unsigned int)minor_S(*dev);
    ldev->unit = (unsigned int)unit_S(*dev);
    return((char *)NULL);
}


/*
 * ExecLsof() -- execute lsof with full field output and a NUL field terminator
 *		 in a child process
 */

char *
ExecLsof(opt)
    char **opt;				/* lsof options -- a pointer to an
					 * array of character pointers,
					 * terminated by a NULL pointer */
{
    static char **av = (char **)NULL;	/* lsof argument vector, dynamically
					 * allocated */
    static int ava = 0;			/* **av entries allocated */
    char buf[2048];			/* temporary buffer */
    char *em;				/* error message pointer */
    int fd;				/* temporary file descriptor */
    int optc;				/* option count */
    int nf;				/* number of files */
    int p[2];				/* pipe FDs */
    char **tcpp;			/* temporary character pointer
					 * pointer */
    int ti;				/* temporary integer */
    int tlen;				/* temporary length */
    pid_t tpid;				/* temporary PID holder */
/*
 * It's an error if lsof is already in execution or if no lsof options
 * were supplied.
 */
    (void) getlsofpath();
    if (LsofPid)
	return("ERROR!!!  ExecLsof() says lsof is already in execution");
    if (!opt)
	return("ERROR!!!  no ExecLsof() option list");
    for (optc = 0, tcpp = opt; *tcpp; optc++, tcpp++)
	;
/*
 * Make sure lsof is executable.
 */
    if ((em = IsLsofExec()))
	return(em);
/*
 * Open a pipe through which lsof can return output.
 */
    if (pipe(p)) {
	(void) snprintf(buf, sizeof(buf) - 1,
	    "ERROR!!!  can't open pipe: %s", strerror(errno));
	return(MkStrCpy(buf, &ti));
    }
/*
 * Allocate and build an argument vector.  The first entry will be set
 * to "lsof", the second to "-wFr", and the third to "-F0".  Additional
 * entries will be set as supplied by the caller.
 */
    if ((optc + 4) > ava) {
	tlen = (int)(sizeof(char *) * (optc + 4));
	if (!av)
	    av = (char **)malloc(tlen);
	else
	    av = (char **)realloc((void *)av, tlen);
	if (!av) {
	    (void) snprintf(buf, sizeof(buf) - 1,
		"LTlib: ExecLsof() can't allocat pointers for %d arguments",
		optc + 4);
	    return(MkStrCpy(buf, &ti));
	}
	ava = optc + 4;
    }
    for (ti = 0, tcpp = opt; ti < (optc + 3); ti++) {
	switch(ti) {
	case 0:
	    av[ti] = "lsof";
	    break;
	case 1:
	    av[ti] = "-wFr";
	    break;
	case 2:
	    av[ti] = "-F0";
	    break;
	default:
	    av[ti] = *tcpp;
	    tcpp++;
	}
    }
    av[ti] = (char *)NULL;
/*
 * Fork a child process to run lsof.
 */
    switch((tpid = fork())) {
    case (pid_t)0:

    /*
     * This is the child process.
     *
     * First close all file descriptors except the output side of the pipe.
     *
     * Make the output side of the pipe STDOUT and STDERR.
     */
	for (fd = 0, nf = getdtablesize(); fd < nf; fd++) {
	    if (fd == p[1])
		continue;
	    (void) close(fd);
	}
	if (p[1] != 1)
	    (void) dup2(p[1], 1);
	if (p[1] != 2)
	    (void) dup2(p[1], 2);
	if ((p[1] != 1) && (p[1] != 2))
	    (void) close(p[1]);
    /*
     * Execute lsof.
     */
	(void) execv(LsofPath, av);
	_exit(0);				/* (Shouldn't get here.) */
    case (pid_t)-1:

    /*
     * A fork error occurred.  Form and return a message.
     */
	(void) snprintf(buf, sizeof(buf) - 1,
	    "ERROR!!!  ExecLsof() can't fork: %s", strerror(errno));
	buf[sizeof(buf) - 1] = '\0';
	return(MkStrCpy(buf, &ti));
    default:

    /*
     * This is the parent.
     *
     * Save the lsof child PID.
     *
     * Close the output side of the pipe.
     *
     * Save the input side of the pipe as LsofFd; open a stream for it.
     */
	LsofPid = tpid;
	(void) close(p[1]);
	LsofFd = p[0];
	if (!(LsofFs = fdopen(LsofFd, "r")))
	    return("ERROR!!!  ExecLsof() can't open stream to lsof output FD");
    }
/*
 * Wait a bit for lsof to start and put something in its pipe, then return
 * an "All is well." response.
 */
    sleep(1);
    return((char *)NULL);
}


/*
 * getlsofpath() -- get lsof path, either from LT_LSOF_PATH in the environment
 *		    or from LT_DEF_LSOF_PATH
 */

static void
getlsofpath()
{
    char *tcp;				/* temporary character pointer */
    int ti;				/* temporary integer */

    if (LsofPath)
	return;
    if ((tcp = getenv("LT_LSOF_PATH")))
	LsofPath = MkStrCpy(tcp, &ti);
    else
	LsofPath = LT_DEF_LSOF_PATH;
}


/*
 * GetOpt() -- Local get option
 *
 * Borrowed from lsof's main.c source file.
 *
 * Liberally adapted from the public domain AT&T getopt() source,
 * distributed at the 1985 UNIFORM conference in Dallas
 *
 * The modifications allow `?' to be an option character and allow
 * the caller to decide that an option that may be followed by a
 * value doesn't have one -- e.g., has a default instead.
 */

static int
GetOpt(ct, opt, rules, em, pn)
    int ct;				/* option count */
    char *opt[];			/* options */
    char *rules;			/* option rules */
    char **em;				/* error message return */
    char *pn;
{
    register int c;			/* character value */
    register char *cp = (char *)NULL;	/* character pointer */
    char embf[2048];			/* error message buffer */
    int tlen;				/* temporary message length from
					 * MkStrCpy() */

    *em = (char *)NULL;
    if (GOx2 == 0) {

    /*
     * Move to a new entry of the option array.
     *
     * EOF if:
     *
     *	Option list has been exhausted;
     *	Next option doesn't start with `-' or `+';
     *	Next option has nothing but `-' or `+';
     *	Next option is ``--'' or ``++''.
     */
	if (GOx1 >= ct
	||  (opt[GOx1][0] != '-' && opt[GOx1][0] != '+')
	||  !opt[GOx1][1])
	    return(EOF);
	if (strcmp(opt[GOx1], "--") == 0 || strcmp(opt[GOx1], "++") == 0) {
	    GOx1++;
	    return(EOF);
	}
	GOx2 = 1;
    }
/*
 * Flag `:' option character as an error.
 *
 * Check for a rule on this option character.
 */
    if ((c = opt[GOx1][GOx2]) == ':') {
	(void) snprintf(embf, sizeof(embf) - 1,
	    "ERROR!!!  colon is an illegal option character.");
	embf[sizeof(embf) - 1] = '\0';
	*em = MkStrCpy(embf, &tlen);
    } else if (!(cp = strchr(rules, c))) {
	(void) snprintf(embf, sizeof(embf) - 1,
	    "ERROR!!!  illegal option character: %c", c);
	embf[sizeof(embf) - 1] = '\0';
	*em = MkStrCpy(embf, &tlen);
    }
    if (*em) {

    /*
     * An error was detected.
     *
     * Advance to the next option character.
     *
     * Return the character causing the error.
     */
	if (opt[GOx1][++GOx2] == '\0') {
	    GOx1++;
	    GOx2 = 0;
	}
	return(c);
    }
    if (*(cp + 1) == ':') {

    /*
     * The option may have a following value.  The caller decides if it does.
     *
     * Don't indicate that an option of ``--'' is a possible value.
     *
     * Finally, on the assumption that the caller will decide that the possible
     * value belongs to the option, position to the option following the
     * possible value, so that the next call to GetOpt() will find it.
     */
	if(opt[GOx1][GOx2 + 1] != '\0') {
	    GOv = &opt[GOx1++][GOx2];
	} else if (++GOx1 >= ct)
	    GOv = (char *)NULL;
	else {
	    GOv = opt[GOx1];
	    if (strcmp(GOv, "--") == 0)
		GOv = (char *)NULL;
	    else
		GOx1++;
	}
	GOx2 = 0;
     } else {

    /*
     * The option character stands alone with no following value.
     *
     * Advance to the next option character.
     */
	if (opt[GOx1][++GOx2] == '\0') {
	    GOx2 = 0;
	    GOx1++;
	}
	GOv = (char *)NULL;
    }
/*
 * Return the option character.
 */
    return(c);
}


/*
 * IsLsofExec() -- see if lsof is executable
 */

char *
IsLsofExec()
{
    char buf[2048];			/* temporary buffer */
    int len;				/* temporary length */

    (void) getlsofpath();
    if (access(LsofPath, X_OK) < 0) {
	(void) snprintf(buf, sizeof(buf) - 1,
	    "ERROR!!!  can't execute %s: %s", LsofPath, strerror(errno));
        return(MkStrCpy(buf, &len));
    }
    return((char *)NULL);
}


/*
 * LTlibClean() -- clean up LTlib resource accesses
 */

void
LTlibClean()
{
    (void) StopLsof();
}


/*
 * MkStrCpy() -- make string copy
 */

char *
MkStrCpy(src, len)
    char *src;			/* string source to copy */
    int *len;			/* returned length allocation */
{
    char *rp;			/* return pointer */
    int srclen;			/* source string length */

    if (!src) {
	(void) fprintf(stderr, "ERROR!!!  no string supplied to MkStrCpy()\n");
	exit(1);
    }
    srclen = (int)strlen(src);
    *len = srclen++;
    if (!(rp = (char *)malloc(srclen))) {
	(void) fprintf(stderr, "ERROR!!!  MkStrCpy() -- no malloc() space");
	exit(1);
    }
    (void) strcpy(rp, src);
    return(rp);
}


/*
 * PrtMsg() -- print message
 */

void
PrtMsg(mp, pn)
    char *mp;				/* message pointer -- may be NULL to
					 * trigger space prefix initialization
					 */
    char *pn;				/* program name */
{
    static int pfxlen = -1;		/* prefix length, based on program */
					/* name -- computed on first call
					 * when pfxlen == -1 */
    static char *pfx = (char *)NULL;	/* prefix (spaces) */
    int ti;				/* temporary index */

    if (pfxlen == -1) {

    /*
     * This is the first call.  Compute the prefix length and build the
     * prefix.
     */
	if (!pn)
	    pfxlen = 0;
	else
	    pfxlen = (int)(strlen(pn));
	pfxlen += (int)strlen(" ... ");
	if (!(pfx = (char *)malloc(pfxlen + 1))) {
	    (void) printf( "ERROR!!!  not enough space for %d space prefix\n",
		pfxlen);
	    exit(1);
	}
	for (ti = 0; ti < pfxlen; ti++) {
	    pfx[ti] = ' ';
	}
	pfx[pfxlen] = '\0';
	MsgStat = 0;
    }
/*
 * Process the message.
 */
    if (MsgStat)
	(void) printf("%s", pfx);
    if (mp && *mp) {
	(void) printf("%s\n", mp);
	MsgStat = 1;
    }
}


/*
 * PrtMsgX() -- print message and exit
 */

void
PrtMsgX(mp, pn, f, xv)
    char *mp;				/* message pointer */
    char *pn;				/* program name */
    void (*f)();			/* clean-up function pointer */
    int xv;				/* exit value */
{
    if (mp)
	PrtMsg(mp, pn);
    if (f)
	(void) (*f)();
    (void) LTlibClean();
    exit(xv);
}


/*
 * RdFrLsof() -- read from lsof
 */

LTfldo_t *
RdFrLsof(nf, em)
    int *nf;				/* number of fields receiver */
    char **em;				/* error message pointer receiver */
{
    char buf[2048];			/* temporary buffer */
    int bufl = (int)sizeof(buf);	/* size of buf[] */
    char *blim = &buf[bufl - 1];	/* buf[] limit (last character
					 * address) */
    char *fsp;				/* field start pointer */
    char *tcp;				/* temporary character pointer */
    LTfldo_t *tfop;			/* temporary field output pointer */
    int ti;				/* temporary index */
    int tlen;				/* remporary length */
    char *vp;				/* value character pointer */
/*
 * Check for errors.
 */
    if (!em)
	return((LTfldo_t *)NULL);
    if (!nf) {
	*em = "ERROR!!!  RdFrLsof() not given a count return pointer";
	return((LTfldo_t *)NULL);
    }
    *em = (char *)NULL;
    *nf = 0;
/*
 * If fields are in use, release their resources.
 */
    for (ti = 0, tfop = Fo; (ti < Ufo); ti++, tfop++) {
	if (tfop->v)
	    (void) free((void *)tfop->v);
    }
    Ufo = 0;
/*
 * Read a line from lsof.
 */
    if (!fgets(buf, bufl - 2, LsofFs)) {

    /*
     * An lsof pipe EOF has been reached.  Indicate that with a NULL
     * pointer return, coupled with a NULL error message return pointer
     * (set above), and a field count of zero (set above).
     */
	return((LTfldo_t *)NULL);
    }
/*
 * Parse the lsof line, allocating field output structures as appropriate.
 *
 * It is expected that fields will end in a NUL ('\0') or a NL ('\0') and
 * that a NL ends all fields in the lsof line.
 */
    for (tcp = buf, Ufo = 0; (*tcp != '\n') && (tcp < blim); tcp++) {

    /*
     * Start a new field.  The first character is the LSOF_FID_*.
     *
     * First allocate an LTfldo_t structure.
     */
	if (Ufo >= Afo) {

	/*
	 * More LTfldo_t space is required.
	 */
	     Afo += LT_FLDO_ALLOC;
	     tlen = (int)(Afo * sizeof(LTfldo_t));
	     if (Fo)
		Fo = (LTfldo_t *)realloc(Fo, tlen);
	     else
		Fo = (LTfldo_t *)malloc(tlen);
	    if (!Fo) {

	    /*
	     * A serious error has occurred; no LTfldo_t space is available.
	     */
		(void) snprintf(buf, bufl,
		    "ERROR!!!  RdFrLsof() can't allocate %d pointer bytes",
		    tlen);
		*em = MkStrCpy(buf, &ti);
		*nf = -1;
		return((LTfldo_t *)NULL);
	    }
	}
	tfop = Fo + Ufo;
	tfop->v = (char *)NULL;
	Ufo++;
    /*
     * Save the LSOF_FID_* character.  Then compute the field value length,
     * and make a copy of it.
     */
	tfop->ft = *tcp++;
	fsp = tcp;
	tlen = 0;
	while (*tcp && (*tcp != '\n') && (tcp < blim)) {
	     tcp++;
	     tlen++;
	}
	if (!(vp = (char *)malloc(tlen + 1))) {

	/*
	 * A serious error has occurred; there's no space for the field value.
	 */
	    (void) snprintf(buf, bufl,
		"ERROR!!!  RdFrLsof() can't allocate %d field bytes", tlen + 1);
	    *em = MkStrCpy(buf, &ti);
	    *nf = -1;
	    return((LTfldo_t *)NULL);
	}
	(void) memcpy((void *)vp, (void *)fsp, tlen);
	vp[tlen] = '\0';
	tfop->v = vp;
	if (*tcp == '\n')
	    break;
	if (tcp >= blim) {

	/*
	 * The lsof line has no NL terminator; that's an error.
	 */
	    *em = "ERROR!!! RdFrLsof() didn't find a NL";
	    *nf = -1;
	    return((LTfldo_t *)NULL);
	}
    }
/*
 * The end of the lsof line has been reached.  If no fields were assembled,
 * return an error indicate.  Otherwise return the fields and their count.
 */
    if (!Ufo) {
	*em = "ERROR!!! RdFrLsof() read an empty lsof line";
	*nf = -1;
	return((LTfldo_t *)NULL);
    }
    *nf = Ufo;
    *em = (char *)NULL;
    return(Fo);
}


/*
 * ScanArg() -- scan arguments
 */

int
ScanArg(ac, av, opt, pn)
    int ac;				/* argument count */
    char *av[];				/* argument pointers */
    char *opt;				/* option string */
    char *pn;				/* program name */
{
    char *em;				/* pointer to error message returned by
					 * GetOpt() */
    char embf[2048];			/* error message buffer */
    int rv = 0;				/* return value */
    int tc;				/* temporary character value */
/*
 * Preset possible argument values.
 */
    LTopt_h = 0;
    if (LTopt_p) {
	(void) free((void *)LTopt_p);
	LTopt_p = (char *)NULL;
    }
/*
 * Process the options according to the supplied option string.
 */
    while ((tc = GetOpt(ac, av, opt, &em, pn)) != EOF) {
	if (em) {
	    rv = 1;
	    PrtMsg(em, pn);
	    continue;
	}
	switch (tc) {
	case 'h':
	    LTopt_h = 1;
	    break;
	case 'p':
	    if (!GOv || *GOv == '-' || *GOv == '+') {
		rv = 1;
		(void) PrtMsg("ERROR!!!  -p not followed by a path", pn);
	    } else
		LTopt_p = GOv;
	    break;
	default:
	    rv = 1;
	    (void) snprintf(embf, sizeof(embf) - 1,
		"ERROR!!!  unknown option: %c", tc);
	    PrtMsg(embf, pn);
	}
    }
    for (; GOx1 < ac; GOx1++) {

    /*
     * Report extraneous arguments.
     */
	rv = 1;
	(void) snprintf(embf, sizeof(embf) - 1,
	    "ERROR!!!  extraneous option: \"%s\"", av[GOx1]);
	PrtMsg(embf, pn);
    }
    return(rv);
}


/*
 * StopLsof() -- stop a running lsof process and close the pipe from it
 */

void
StopLsof()
{
    pid_t pid;

    if (LsofPid) {

    /*
     * An lsof child process may be active.  Wait for (or kill) it.
     */
	pid = wait3(NULL, WNOHANG, NULL);
	if (pid != LsofPid) {
	    (void) kill(LsofPid, SIGKILL);
	    sleep(2);
	    pid = wait3(NULL, WNOHANG, NULL);
	}
	LsofPid = (pid_t)0;
    }
    (void) closepipe();
}


/*
 * x2dev() -- convert hex string to device number
 */

static X2DEV_T
x2dev(x, em)
    char *x;				/* hex string */
    char **em;				/* error message receiver */
{
    char buf[2048];			/* temporary message buffer */
    int c;				/* character holder */
    X2DEV_T dev;			/* device number result */
    char *wx;				/* working hex string pointer */
    int xl;				/* hex string length */

    if (!x || !*x) {
	*em = "ERROR!!!  no hex string supplied to x2dev()";
	return(0);
    }
    wx = strncasecmp(x, "0x", 2) ? x : (x + 2);
    if (((xl = (int)strlen(wx)) < 1) || (xl > XDINDEV)) {
	(void) snprintf(buf, sizeof(buf) - 1,
	    "ERROR!!!  x2dev(\"%s\") bad length: %d", x, xl + 2);
	buf[sizeof(buf) - 1] = '\0';
	*em = MkStrCpy(buf, &c);
	return(0);
    }
/*
 * Assemble the device number result from the hex string.
 */
    for (dev = (X2DEV_T)0; *wx; wx++) {
	if (isdigit((unsigned char)*wx)) {
	    dev = (dev << 4) | (unsigned int)(((int)*wx - (int)'0') & 0xf);
	    continue;
	}
	c = (int) tolower((unsigned char)*wx);
	if ((c >= (int)'a') && (c <= (int)'f')) {
	    dev = (dev << 4) | (unsigned int)((c - 'a' + 10) & 0xf);
	    continue;
	}
	(void) snprintf(buf, sizeof(buf) - 1,
	    "ERROR!!!  x2dev(\"%s\") non-hex character: %c", x, c);
	*em = MkStrCpy(buf, &c);
    }
/*
 * Return result and no error indication.
 */
    *em = (char *)NULL;
    return(dev);
}