/*
 * arg.c - common argument processing support 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";
static char *rcsid = "$Id: arg.c,v 1.33 96/02/21 09:18:21 abe Exp $";
#endif


#include "lsof.h"
#include "version.h"


_PROTOTYPE(static int ckfd_range,(char *first, char *dash, char *last, int *lo, int *hi));
_PROTOTYPE(static char *isnullstr,(char *s));


/*
 * ckfd_range() - check fd range
 */

static int
ckfd_range(first, dash, last, lo, hi)
	char *first;			/* starting character */
	char *dash;			/* '-' location */
	char *last;			/* '\0' location */
	int *lo;			/* returned low value */
	int *hi;			/* returned high value */
{
	char *cp;
/*
 * See if the range character pointers make sense.
 */
	if (first >= dash || dash >= last) {
	    (void) fprintf(stderr, "%s: illegal FD range for -d: %s\n",
		Pn, first);
	    return(1);
	}
/*
 * Assemble and check the high and low values.
 */
	for (cp = first, *lo = 0; *cp && cp < dash; cp++) {
	    if (!isdigit(*cp)) {

FD_range_nondigit:

		(void) fprintf(stderr, "%s: non-digit in -d FD range: %s\n",
		    Pn, first);
		return(1);
	    }
	    *lo = (*lo * 10) + (int)(*cp - '0');
	}
	for (cp = dash+1, *hi = 0; *cp && cp < last; cp++) {
	    if (!isdigit(*cp))
		goto FD_range_nondigit;
	    *hi = (*hi * 10) + (int)(*cp - '0');
	}
	if (*lo >= *hi) {
	    (void) fprintf(stderr, "%s: -d FD range's low >= its high: %s\n",
		Pn, first);
	    return(1);
	}
	return(0);
}


#if	defined(HASDCACHE)
/*
 * ctrl_dcache() - enter device cache control
 */

int
ctrl_dcache(c)
	char *c;			/* control string */
{
	MALLOC_S len;
	int rc = 0;
	
	if (c == NULL) {
		(void) fprintf(stderr,
			"%s: no device cache option control string\n", Pn);
		return(1);
	}
/*
 * Decode argument function character.
 */
	switch (*c) {
	case '?':
		if (*(c+1) != '\0') {
			(void) fprintf(stderr,
				"%s: nothing should follow -D?\n", Pn);
			return(1);
		}
		DChelp = 1;
		return(0);
	case 'b':
	case 'B':
		if (Setuidroot

#if	!defined(WILLDROPGID)
		||  Myuid
#endif	/* !defined(WILLDROPGID) */

		)
			rc = 1;
		else
			DCstate = 1;
		break;
	case 'r':
	case 'R':
		if (Setuidroot && *(c+1))
			rc = 1;
		else
			DCstate = 2;
		break;
	case 'u':
	case 'U':
		if (Setuidroot

#if	!defined(WILLDROPGID)
		||  Myuid
#endif	/* !defined(WILLDROPGID) */

		)
			rc = 1;
		else
			DCstate = 3;
		break;
	case 'i':
	case 'I':
		if (*(c+1) == '\0') {
			DCstate = 0;
			return(0);
		}
		/* fall through */
	default:
		(void) fprintf(stderr, "%s: unknown -D option: %s\n", Pn, c);
		return(1);
	}
	if (rc) {
		(void) fprintf(stderr,
			"%s: -D option restricted to root: %s\n", Pn, c);
		return(1);
	}
/*
 * Skip to optional path name and save it.
 */
	for (c++; *c && (*c == ' ' || *c == '\t'); c++)
		;
	if ((len = strlen(c))) {
		if ((DCpathArg = (char *)malloc(len + 1)) == NULL) {
			(void) fprintf(stderr,
				"%s: no space for -D path: %s\n", Pn, c);
			exit(1);
		}
		(void) strcpy(DCpathArg, c);
	}
	return(0);
}
#endif	/* defined(HASDCACHE) */


/*
 * enter_fd() - enter file descriptor list for searching
 */

int
enter_fd(f)
	char *f;			/* file descriptor list pointer */
{
	char buf[32], c, *cp1, *cp2, *cp3, *dash;
	int err, hi, lo;
	char *fc;
	MALLOC_S len;
/*
 *  Check for non-empty list and make a copy.
 */
	if (f == (char *)NULL || (len = strlen(f) + 1) < 2) {
	    (void) fprintf(stderr, "%s: no file descriptor specified\n", Pn);
	    return(1);
	}
	if ((fc = (char *)malloc(len)) == (char *)NULL) {
	    (void) fprintf(stderr, "%s: no space for %d byte fd string: %s\n",
		Pn, len, f);
	    exit(1);
	}
	(void) strcpy(fc, f);
/*
 * Isolate each file descriptor in the comma-separated list, then enter it
 * in the file descriptor string list.  If a descriptor has the form:
 *
 *	[0-9]+-[0-9]+
 *
 * treat it as an ascending range of file descriptor numbers.
 */
	for (cp1 = fc, err = 0; *cp1;) {
	    for (cp2 = cp1, dash = (char *)NULL; *cp2 && *cp2 != ','; cp2++) {
		if (*cp2 == '-')
		    dash = cp2;
	    }
	    if ((c = *cp2) != '\0')
		*cp2 = '\0';
	    if (cp2 > cp1) {
		if (dash) {
		    if (ckfd_range(cp1, dash, cp2, &lo, &hi))
			err = 1;
		    else {
			while (lo <= hi) {
			    (void) sprintf(buf, "%d", lo);
			    if (enter_str_lst("d", buf, &Fdl)) {
				err = 1;
				break;
			    }
			    lo++;
			}
		    }
		} else {
		    if (enter_str_lst("d", cp1, &Fdl))
			err = 1;
		}
	    }
	    if (c == '\0')
		break;
	    cp1 = cp2 + 1;
	}
	(void) free((FREE_P *)fc);
	return(err);
}


/*
 * enter_id() - enter PGRP or PID for searching
 */

int
enter_id(ty, p)
	enum IDType ty;			/* type: PGRP or PID */
	char *p;			/* process group ID string pointer */
{
	char *cp;
	int i, id, mx, n, *s;

	if (p == NULL) {
		(void) fprintf(stderr, "%s: no process%s ID specified\n",
			Pn, (ty == PGRP) ? " group" : "");
		return(1);
	}
/*
 * Set up variables for the type of ID.
 */
	switch (ty) {
	case PGRP:
		mx = Mxpgrp;
		n = Npgrp;
		s = Spgrp;
		break;
	case PID:
		mx = Mxpid;
		n = Npid;
		s = Spid;
		break;
	default:
		(void) fprintf(stderr,
			"%s: enter_id \"%s\", invalid type: %d\n", Pn, p, ty);
		exit(1);
	}
/*
 * Convert and store the ID.
 */
	for (cp = p; *cp;) {

	/*
	 * Assemble ID.
	 */
		for (id = 0; *cp && *cp != ','; *cp++) {

#if	defined(__STDC__)
			if ( ! isdigit(*cp))
#else
			if ( ! isascii(*cp) || ! isdigit(*cp))
#endif	/* __STDC__ */

			{
				(void) fprintf(stderr,
					"%s: illegal process%s ID: %s\n",
					Pn, (ty == PGRP) ? " group" : "", p);
				return(1);
			}
			id = (id * 10) + *cp - '0';
		}
		if (*cp)
			cp++;
	/*
	 * Avoid entering duplicates.
	 */
		for (i = 0; i < n; i++) {
			if (id == s[i])
				break;
		}
		if (i < n)
			continue;
	/*
	 * Allocate table table space.
	 */
		if (n >= mx) {
			mx += IDINCR;
			if (s == NULL)
			    s = (int *)malloc((MALLOC_S)(sizeof(int *) * mx));
			else
			    s = (int *)realloc((MALLOC_P *)s,
					(MALLOC_S)(sizeof(int *) * mx));
			if (s == NULL) {
			    (void) fprintf(stderr,
				"%s: no space for %d process%s IDs",
				Pn, mx, (ty == PGRP) ? " group" : "");
			    exit(1);
			}
		}
		s[n++] = id;
	}
/*
 * Save variables for the type of ID.
 */
	if (ty == PGRP) {
		Mxpgrp = mx;
		Npgrp = n;
		Spgrp = s;
	} else {
		Mxpid = mx;
		Npid = Npuns = n;
		Spid = s;
	}
	return(0);
}


/*
 * enter_network_address() - enter Internet address for searching
 */

int
enter_network_address(na)
	char *na;			/* Internet address string pointer */
{
	unsigned char *ap;
	int at = 0;
	struct hostent *he;
	char *hn = NULL;
	MALLOC_S l;
	struct nwad *n = NULL;
	struct nwad *nn;
	char *p, *wa;
	struct servent *se;

	if (na == NULL) {
		(void) fprintf(stderr, "%s: no network address specified\n",
			Pn);
		return(1);
	}
/*
 * Allocate and initialize network address structure.
 */
	if ((n = (struct nwad *)malloc(sizeof(struct nwad))) == NULL) {

no_nwad_space:

	    (void) fprintf(stderr,
		"%s: no space for network address from \"%s\"\n", Pn, na);
	    exit(1);
	}
	n->proto = NULL;
	n->a[0] = n->a[1] = n->a[2] = n->a[3] = 0;
	n->port = -1;
/*
 * Process protocol name, optionally followed by a '@' and a host name or
 * Internet address, or a ':' and a service name or port number.
 */
	wa = na;
	if (*wa && *wa != '@' && *wa != ':') {
	    for (p = wa; *wa && *wa != '@' && *wa != ':'; wa++)
		;
	    if ((l = wa - p)) {
		if ((n->proto = (char *)malloc(l + 1)) == NULL) {
		    (void) fprintf(stderr,
			"%s: no space for protocol name from \"%s\"\n",
			Pn, na);
nwad_exit:
		    if (n->proto)
			(void) free((FREE_P *)n->proto);
		    if (hn)
			(void) free((FREE_P *)hn);
		    (void) free((FREE_P *)n);
		    return(1);
		}
		(void) strncpy(n->proto, p, l);
		n->proto[l] = '\0';
	    /*
	     * Convert protocol name to lower case.
	     */
		for (p = n->proto; *p; p++) {
		    if (*p >= 'A' && *p <= 'Z')
			*p = *p - 'A' + 'a';
		}
	    }
	}
/*
 * Process an Internet address (1.2.3.4) or host name, preceded by a '@'
 * and optionally followed by a colon and a service name or port number.
 */
	if (*wa == '@') {
	    wa++;
	    if ( ! *wa || *wa == ':') {

unacc_address:
		(void) fprintf(stderr,
		    "%s: unacceptable Internet address in \"%s\"\n", Pn, na);
		goto nwad_exit;
	    }
	    if (*wa < '0' || *wa > '9') {
	/*
	 * Assemble host name.
	 */
		for (p = wa; *p && *p != ':'; p++)
		    ;
		if ((l = p - wa)) {
		    if ((hn = (char *)malloc(l + 1)) == NULL) {
			fprintf(stderr,
			    "%s: no space for host name from \"%s\"\n", Pn, na);
			goto nwad_exit;
		    }
		    (void) strncpy(hn, wa, l);
		    hn[l] = '\0';
		    if ((he = gethostbyname(hn)) == NULL) {
			fprintf(stderr, "%s: unknown host name in \"%s\"\n",
			    Pn, na);
			goto nwad_exit;
		    }
		    ap = (unsigned char *)he->h_addr;
		    n->a[0] = *ap++;
		    n->a[1] = *ap++;
		    n->a[2] = *ap++;
		    n->a[3] = *ap;
		    at = 1;
		}
		wa = p;
	    } else {
	/*
	 * Assemble Internet address.
	 */
		for (l = 0; *wa; wa++) {
		    if (*wa == ':')
			break;
		    if (*wa == '.') {
			l++;
			if (l > 3)
			    break;
			continue;
		    }
		    if (*wa < '0' || *wa > '9')
			goto unacc_address;
		    n->a[l] = (10 * n->a[l]) + *wa - '0';
		    if (n->a[l] > 255)
			goto unacc_address;
		}
		if (l != 3 || (n->a[0] == 0 && n->a[1] == 0 && n->a[2] == 0
		&& n->a[3] == 0))
		    goto unacc_address;
	    }
	}
/*
 * Process a service name or port number, preceded by a colon.
 */
	if (*wa && (*wa != ':' || ! *(++wa))) {

unacc_port:
	    (void) fprintf(stderr, "%s: unacceptable port number in \"%s\"\n",
		Pn, na);
	    goto nwad_exit;
	}
	if (*wa && (*wa < '0' || *wa > '9')) {

	/*
	 * Convert service name to port number, using already-specified
	 * protocol name.
	 */
	    if (! n->proto) {
		(void) fprintf(stderr, "%s: must specify protocol in \"%s\"\n",
		    Pn, na);
		goto nwad_exit;
	    }
	    if ((se = getservbyname(wa, n->proto)) == NULL) {
		(void) fprintf(stderr,
		    "%s: unknown service \"%s\" for protocol %s in \"%s\"\n",
		    Pn, wa, n->proto, na);
		goto nwad_exit;
	    }
	    n->port = (int)ntohs(se->s_port);
	} else if (*wa) {

	/*
	 * Assemble port number.
	 */
	    for (n->port = 0; *wa; wa++) {
		if (*wa < '0' || *wa > '9')
		    goto unacc_port;
		n->port = (n->port * 10) + *wa - '0';
	    }
	}
/*
 * Test completed specification -- it must contain at least one of: protocol,
 * Internet address or port.  If correct, link into search list.
 */
	if ( ! n->proto
	&&  n->a[0] == 0 && n->a[1] == 0 && n->a[2] == 0 && n->a[3] == 0
	&&  n->port == -1) {
	    (void) fprintf(stderr,
		"%s: no Internet address specified in \"%s\"\n",
		Pn, na);
	    goto nwad_exit;
	}
	n->next = Nwad;
	Nwad = n;
	if (at) {

	/*
	 * If the network address came from gethostbyname(), enter all
	 * the addresses for the name.
	 */
	    for (; he->h_addr_list[at]; at++) {
		if ((nn = (struct nwad *)malloc(sizeof(struct nwad))) == NULL)
		    goto no_nwad_space;
		*nn = *n;
		ap = (unsigned char *)he->h_addr_list[at];
		nn->a[0] = *ap++;
		nn->a[1] = *ap++;
		nn->a[2] = *ap++;
		nn->a[3] = *ap;
		nn->next = Nwad;
		Nwad = nn;
	    }
	}
	return(0);
}


/*
 * enter_str_lst() - enter a string on a list
 */

int
enter_str_lst(opt, s, lp)
	char *opt;			/* option name */
	char *s;			/* string to enter */
	struct str_lst **lp;		/* string's list */
{
	char *cp;
	MALLOC_S len;
	struct str_lst *lpt;

	if (s == NULL || *s == '-') {
		(void) fprintf(stderr, "%s: missing %s option value\n",
			Pn, opt);
		return(1);
	}
	len = strlen(s);
	if ((cp = (char *)malloc(len + 1)) == NULL) {
		(void) fprintf(stderr, "%s: no string copy space: %s\n",
			Pn, s);
		return(1);
	}
	if ((lpt = (struct str_lst *)malloc(sizeof(struct str_lst))) == NULL) {
		(void) fprintf(stderr, "%s: no list space: %s\n", Pn, s);
		return(1);
	}
	(void) strcpy(cp, s);
	lpt->str = cp;
	lpt->len = (int)len;
	lpt->next = *lp;
	*lp = lpt;
	return(0);
}


/*
 * enter_uid() - enter User Identifier for searching
 */

int
enter_uid(u)
	char *u;			/* User IDentifier string pointer */
{
	int err, i, nn;
	char lnm[LOGINML];
	struct passwd *pw;
	char *s;
	uid_t uid;

	if (u == NULL) {
		(void) fprintf(stderr, "%s: no UIDs specified\n", Pn);
		return(1);
	}
	for (err = 0, s = u; *s;) {

	/*
	 * Assemble next User IDentifier.
	 */
		for (i = nn = uid = 0; *s && *s != ','; i++, s++) {
			if (i >= LOGINML-1) {
				(void) fprintf(stderr,
					"%s: illegal UID in %s\n", Pn, u);
				return(1);
			}
			lnm[i] = *s;
			if (nn)
				continue;

#if	defined(__STDC__)
			if (isdigit(*s))
#else
			if (isascii(*s) && isdigit(*s))
#endif	/* __STDC__ */

				uid = (uid * 10) + *s - '0';
			else
				nn++;
		}
		if (*s)
			s++;
		if (nn) {
		       lnm[i] = '\0';

#if	defined(HASPWSTAYOPEN)
			pw_stay_open();
#endif

			if ((pw = getpwnam(lnm)) == NULL) {
				(void) fprintf(stderr,
					"%s: can't get UID for %s\n",
					Pn, lnm);
				err = 1;
				continue;
			} else
				uid = pw->pw_uid;
		}

#if	defined(HASSECURITY)
	/*
	 * If the security mode is enabled, only the root user may list files
	 * belonging to user IDs other than the real user ID of this lsof
	 * process.
	 */
		if (Myuid && uid != Myuid) {
		    (void) fprintf(stderr,
		    "%s: ID %d request rejected because of security mode.\n",
			Pn, uid);
		    err = 1;
		    continue;
		}
#endif	/* HASSECURITY */

	/*
	 * Avoid entering duplicates.
	 */
		for (i = 0; i < Nuid; i++) {
			if (uid == Suid[i])
				break;
		}
		if (i < Nuid)
			continue;
	/*
	 * Allocate space for User IDentifier.
	 */
		if (Nuid >= Mxuid) {
			Mxuid += UIDINCR;
			if (Suid == NULL)
				Suid = (uid_t *)malloc((MALLOC_S)
					(sizeof(uid_t) * Mxuid));
			else
				Suid = (uid_t *)realloc((MALLOC_P *)Suid,
					(MALLOC_S)(sizeof(uid_t) * Mxuid));
			if (Suid == NULL) {
				(void) fprintf(stderr, "%s: no space for UIDs",
					Pn);
				exit(1);
			}
		}
		Suid[Nuid++] = uid;
	}
	return(err);
}


/*
 * isnullstr() - is it a null string?
 */

static char *
isnullstr(s)
	char *s;			/* string pointer */
{
	if (!s)
		return((char *)NULL);
	while (*s) {
		if (*s != ' ')
			return(s);
		s++;
	}
	return((char *)NULL);
}


/*
 * usage() - display usage and exit
 */

void
usage(xv, fh, version)
	int xv;				/* exit value */
	int fh;				/* ``-F ?'' status */
	int version;			/* ``-v'' status */
{
	char buf[MAXPATHLEN+1], *cp, *cp1, *cp2;
	int dx = -2;
	int  i;

	if (Fhelp || xv) {
	    (void) fprintf(stderr, "%s %s usage: [-?ab%shlnNoOPstUvw%s]",
		Pn, LSOF_VERSION,

#if	defined(HASNCACHE)
		"C",
#else	/* !defined(HASNCACHE) */
		"",
#endif	/* defined(HASNCACHE) */

#if	defined(HASXOPT)
# if	defined(HASXOPT_ROOT)
		(Myuid == 0) ? "X" : ""
# else	/* !defined(HASXOPT_ROOT) */
		"X"
# endif	/* defined(HASXOPT_ROOT) */
#else	/* !defined(HASXOPT) */
		""
#endif	/* defined(HASXOPT) */

	    );

#if	defined(HAS_AFS) && defined(HASAOPT)
	    (void) fprintf(stderr, " [-A A]");
#endif	/* defined(HAS_AFS) && defined(HASAOPT) */

	    (void) fprintf(stderr, " [-c c] [-d s]");

#if	defined(HASDCACHE)
	    (void) fprintf(stderr, " [-D D]");
#endif	/* defined(HASDCACHE) */

	    (void) fprintf(stderr, " [-F [f]]\n [-g [s]] [-i i]");

#if	defined(HASKOPT)
	    (void) fprintf(stderr, " [-k k]");
#endif	/* defined(HASKOPT) */

#if	defined(HASMOPT)
	    (void) fprintf(stderr, " [-m m]");
#endif	/* defined(HASMOPT) */

	    (void) fprintf(stderr,
		" [-p s] [-r [t]] [-S [t]] [-u s] [-U] [--]\n [names]\n");
	}
	if (xv && !Fhelp) {
	    (void) fprintf(stderr,
		"Use the ``-h'' option to get more help information.\n");
	    if (!fh)
    		exit(xv);
	}
	if (Fhelp) {
	    (void) fprintf(stderr, "Defaults are enclosed in parentheses.");
	    (void) fprintf(stderr,
		"  Separate items in a set (s) by commas.\n");
	    (void) fprintf(stderr, "  %-37.37s",
		"-?     list help");
	    (void) fprintf(stderr,
		"-a     AND selections (OR)\n");

#if	defined(HAS_AFS) && defined(HASAOPT)
	    (void) fprintf(stderr,
		"  -A A	  AFS name list file (default = %s)\n",
		AFSAPATHDEF);
#endif	/* defined(HAS_AFS) && defined(HASAOPT) */

	    (void) fprintf(stderr, "  %-37.37s",
		"-b     avoid kernel blocks");
	    (void) fprintf(stderr,
		"-c c   list command c\n");

#if	defined(HASNCACHE) || defined(HASDCACHE)
# if	defined(HASNCACHE)
	    (void) strcpy(buf, "-C     ignore kernel's name cache");
# else	/* !defined(HASNCACHE) */
	    buf[0] = '\0';
# endif	/* defined(HASNCACHE) */
	    (void) fprintf(stderr, "  %-37.37s", buf);
# if	defined(HASDCACHE)
	    if (Setuidroot)
		cp = "?|i|r";

#  if	!defined(WILLDROPGID)
	    else if (Myuid)
		cp = "?|i|r<path>";
#  endif	/* !defined(WILLDROPGID) */

	    else
		cp = "?|i|b|r|u[path]";
	    (void) fprintf(stderr, "-D D   %s\n", cp);
# endif	/* defined(HASDCACHE) */
# else	/* !defined(HASDCACHE) */
	    putc('\n', stderr);
#endif	/* defined(HASNCACHE) || defined(HASDCACHE) */

	    (void) fprintf(stderr, "  %-37.37s",
		"-d s   select by FD set");
	    (void) fprintf(stderr,
		"-F [f] select fields (-F? for help)\n");
	    (void) fprintf(stderr,
		"  -g [s] select by process group ID set and print process group IDs\n");
	    (void) fprintf(stderr, "  %-37.37s",
		"-h     list help");
	    (void) fprintf(stderr,
		"-i     select Internet files\n");
	    (void) fprintf(stderr,
		"  -i i   select by Internet address:");
	    (void) fprintf(stderr,
		" [protocol][@name|number][:service|port]\n");

#if	defined(HASKOPT) || defined(HASMOPT)
# if	defined(HASKOPT)
	    (void) sprintf(buf, "-k k   kernel sym (%s)", N_UNIX);
# else	/* !defined(HASKOPT) */
	    buf[0] = '\0';
# endif	/* defined(HASKOPT) */
# if	defined(HASMOPT)
	    (void) fprintf(stderr, "  %-37.37s", buf);
	    (void) fprintf(stderr, "-m m   kernel mem (%s)\n", KMEM);
# else	/* !defined(HASMOPT) */
	    (void) fprintf(stderr, "  %s\n", buf);
# endif	/* defined(HASMOPT) */
#endif	/* HASCOPT || HASKOPT */

	    (void) fprintf(stderr, "  %-37.37s",
		"-l     list UID numbers");
	    (void) fprintf(stderr,
		"-n     don't list host names\n");
	    (void) fprintf(stderr, "  %-37.37s",
		"-N     select NFS files");
	    (void) fprintf(stderr,
		"-o     always list file offset\n");
	    (void) fprintf(stderr, "  %-37.37s",
		"-O     avoid fork overhead *RISKY*");
	    (void) fprintf(stderr,
		"-p s   select by PID set\n");
	    (void) fprintf(stderr, "  %-37.37s",
		"-P     don't list port names");
	    (void) fprintf(stderr,
		"-r [t] repeat output time (%d)\n", RPTTM);
	    (void) fprintf(stderr, "  %-37.37s",
		"-s     always list file size");
	    (void) fprintf(stderr,
		"-S [t] readlink/stat timeout (%d)\n", TMLIMIT);
	    (void) fprintf(stderr, "  %-37.37s",
		"-t     terse listing");
	    (void) fprintf(stderr,
		"-u s   select by user login/UID set\n");
	    (void) fprintf(stderr, "  %-37.37s",
		"-U     select Unix socket files");
	    (void) fprintf(stderr,
		"-v     display version information\n");

#if	defined(HASXOPT)
	    (void) fprintf(stderr, "  %-37.37s",
		"-w     suppress warning messages");
# if	defined(HASXOPT_ROOT)
	    if (Myuid == 0)
		(void) fprintf(stderr, "-X     %s\n", HASXOPT);
	    else
		(void) fprintf(stderr, "\n");
# else	/* !defined(HASXOPT_ROOT) */
	    (void) fprintf(stderr, "-X     %s\n", HASXOPT);
# endif	/* defined(HASXOPT_ROOT) */
#else	/* !defined(HASXOPT) */
	    (void) fprintf(stderr, "  -w     suppress warning messages\n");
#endif	/* defined(HASXOPT) */

	    (void) fprintf(stderr,
		"  names  select named files or files on named file systems\n");
	    (void) fprintf(stderr, "%s can list all files.",

#if	defined(HASSECURITY)
		"Only root"
#else	/* !defined(HASSECURITY) */
		"Anyone"
#endif	/* defined(HASSECURITY) */

	    );
	    (void) fprintf(stderr, "  Inaccessible /dev warnings are %s.\n",

#if	defined(WARNDEVACCESS)
		"enabled"
#else	/* !defined(WARNDEVACCESS) */
		"disabled"
#endif	/* defined(WARNDEVACCESS) */

	    );

#if defined(HASDCACHE)
# if	defined(HASENVDC) || defined(HASPERSDC) || defined(HASSYSDC)
	    cp = NULL;
#  if	defined(HASENVDC)
	    if (dx == -2 && (dx = dcpath(1)) >= 0)
		cp = DCpath[1];
#  endif	/* defined(HASENVDC) */
#  if	defined(HASSYSDC)
	    if (!cp)
		cp = HASSYSDC;
#  endif	/* defined(HASSYSDC) */
#  if	defined(HASPERSDC)
	    if (!cp && dx != -1 && (dx = dcpath(1)) >= 0)
		cp = DCpath[3];
#  endif	/* defined(HASPERSDC) */
	    if (cp)
		(void) fprintf(stderr,
		    "%s is the default device cache file read path.\n", cp);
# endif    /* defined(HASENVDC) || defined(HASPERSDC) || defined(HASSYSDC) */
#endif	/* defined(HASDCACHE) */

	}
	if (fh) {
	    (void) fprintf(stderr, "%s:\tID    field description\n", Pn);
	    for (i = 0; FieldSel[i].nm; i++) {
		(void) fprintf(stderr, "\t %c    %s\n",
		    FieldSel[i].id, FieldSel[i].nm);
	    }
	}

#if	defined(HASDCACHE)
	if (DChelp) {

	/*
	 * Display device cache file read-only and write paths.
	 */
	    (void) fprintf(stderr, "%s: device cache file read-only paths:\n",
		Pn);
	    if ((dx = dcpath(1)) < 0)
		(void) fprintf(stderr, "\tnone\n");
	    else {
		(void) fprintf(stderr, "\tNamed via -D: %s\n",
		    DCpath[0] ? DCpath[0] : "none");

# if	defined(HASENVDC)
		(void) fprintf(stderr,
		    "\tNamed in environment variable %s: %s\n",
		    HASENVDC, DCpath[1] ? DCpath[1] : "none");
# endif	/* defined(HASENVDC) */

# if	defined(HASSYSDC)
		if (DCpath[2])
		    (void) fprintf(stderr,
			"\tSystem-wide device cache: %s\n", DCpath[2]);
# endif	/* defined(HASSYSDC) */

# if	defined(HASPERSDC)
		(void) fprintf(stderr,
		    "\tPersonal path format (HASPERSDC): \"%s\"\n",
		    HASPERSDC);
#  if	defined(HASPERSDCPATH)
		(void) fprintf(stderr,
		    "\tModified personal path environment variable: %s\n",
		    HASPERSDCPATH);
		cp = getenv(HASPERSDCPATH);
		(void) fprintf(stderr, "\t%s value: %s\n",
			HASPERSDCPATH, cp ? cp : "none");
#  endif	/* defined(HASPERSDCPATH) */
		(void) fprintf(stderr, "\tPersonal path: %s\n",
		    DCpath[3] ? DCpath[3] : "none");
# endif	/* defined(HASPERSDC) */
	    }
	    (void) fprintf(stderr, "%s: device cache file write paths:\n", Pn);
	    if ((dx = dcpath(2)) < 0)
		(void) fprintf(stderr, "\tnone\n");
	    else {
		(void) fprintf(stderr, "\tNamed via -D: %s\n",
		    DCstate == 2 ? "none"
				 : DCpath[0] ? DCpath[0] : "none");

# if	defined(HASENVDC)
		(void) fprintf(stderr,
		    "\tNamed in environment variable %s: %s\n",
		    HASENVDC, DCpath[1] ? DCpath[1] : "none");
# endif	/* defined(HASENVDC) */

# if	defined(HASPERSDC)
		(void) fprintf(stderr,
		    "\tPersonal path format (HASPERSDC): \"%s\"\n",
		    HASPERSDC);
#  if	defined(HASPERSDCPATH)
		(void) fprintf(stderr,
		    "\tModified personal path environment variable: %s\n",
		    HASPERSDCPATH);
		cp = getenv(HASPERSDCPATH);
		(void) fprintf(stderr, "\t%s value: %s\n",
			HASPERSDCPATH, cp ? cp : "none");
#  endif	/* defined(HASPERSDCPATH) */
		 (void) fprintf(stderr, "\tPersonal path: %s\n",
		    DCpath[3] ? DCpath[3] : "none");
# endif	/* defined(HASPERSDC) */
	    }
	}
#endif	/* defined(HASDCACHE) */

	if (version) {

	/*
	 * Display version information in reponse to ``-v''.
	 */
		(void) fprintf(stderr, "%s version information:\n", Pn);
		(void) fprintf(stderr, "\trevision: %s\n", LSOF_VERSION);
		if ((cp = isnullstr(LSOF_CCDATE)))
			(void) fprintf(stderr, "\tconstructed: %s\n", cp);
		cp = isnullstr(LSOF_HOST);
		if (!(cp1 = isnullstr(LSOF_LOGNAME)))
			cp1 = isnullstr(LSOF_USER);
		if (cp || cp1) {
			if (cp && cp1)
				cp2 = "by and on";
			else if (cp)
				cp2 = "on";
			else
				cp2 = "by";
			(void) fprintf(stderr, "\tconstructed %s: %s%s%s\n",
				cp2,
				cp1 ? cp1 : "",
				cp1 ? "@" : "",
				cp  ? cp  : ""
			);
		}
		if ((cp = isnullstr(LSOF_CC)))
			(void) fprintf(stderr, "\tcompiler: %s\n", cp);
		if ((cp = isnullstr(LSOF_CCV)))
			(void) fprintf(stderr, "\tcompiler version: %s\n", cp);
		if ((cp = isnullstr(LSOF_CCFLAGS)))
			(void) fprintf(stderr, "\tcompiler flags: %s\n", cp);
		if ((cp = isnullstr(LSOF_LDFLAGS)))
			(void) fprintf(stderr, "\tloader flags: %s\n", cp);
		if ((cp = isnullstr(LSOF_SYSINFO)))
			(void) fprintf(stderr, "\tsystem info: %s\n", cp);
	}
	exit(xv);
}
