/*
 * misc.c - common miscellaneous 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: misc.c,v 1.28 2014/10/13 22:36:20 abe Exp abe $";
#endif


#include "lsof.h"

#if	defined(HASWIDECHAR)
# if	defined(WIDECHARINCL)
#include WIDECHARINCL
# endif	/* defined(WIDECHARINCL) */
# if	defined(HASWCTYPE_H)
#include <wctype.h>
# endif	/* defined(HASWCTYPE_H) */
#endif	/* defined(HASWIDECHAR) */


/*
 * Local definitions
 */

#if	!defined(MAXSYMLINKS)
#define	MAXSYMLINKS	32
#endif	/* !defined(MAXSYMLINKS) */


/*
 * Local function prototypes
 */

_PROTOTYPE(static void closePipes,(void));
_PROTOTYPE(static int dolstat,(char *path, char *buf, int len));
_PROTOTYPE(static int dostat,(char *path, char *buf, int len));
_PROTOTYPE(static int doreadlink,(char *path, char *buf, int len));
_PROTOTYPE(static int doinchild,(int (*fn)(), char *fp, char *rbuf, int rbln));

#if	defined(HASINTSIGNAL)
_PROTOTYPE(static int handleint,(int sig));
#else	/* !defined(HASINTSIGNAL) */
_PROTOTYPE(static void handleint,(int sig));
#endif	/* defined(HASINTSIGNAL) */


/*
 * Local variables
 */

static pid_t Cpid = 0;			/* child PID */
static jmp_buf Jmp_buf;			/* jump buffer */
static int Pipes[] =			/* pipes for child process */
	{ -1, -1, -1, -1 };
static int CtSigs[] = { 0, SIGINT, SIGKILL };
					/* child termination signals (in order
					 * of application) -- the first is a
					 * dummy to allow pipe closure to
					 * cause the child to exit */
#define	NCTSIGS	(sizeof(CtSigs) / sizeof(int))


#if	defined(HASNLIST)
/*
 * build-Nl() - build kernel name list table
 */

static struct drive_Nl *Build_Nl = (struct drive_Nl *)NULL;
					/* the default Drive_Nl address */

void
build_Nl(d)
	struct drive_Nl *d;		/* data to drive the construction */
{
	struct drive_Nl *dp;
	int i, n;

	for (dp = d, n = 0; dp->nn; dp++, n++)
	    ;
	if (n < 1) {
	    (void) fprintf(stderr,
		"%s: can't calculate kernel name list length\n", Pn);
	    Exit(1);
	}
	if (!(Nl = (struct NLIST_TYPE *)calloc((n + 1),
					       sizeof(struct NLIST_TYPE))))
	{
	    (void) fprintf(stderr,
		"%s: can't allocate %d bytes to kernel name list structure\n",
		Pn, (int)((n + 1) * sizeof(struct NLIST_TYPE)));
	    Exit(1);
	}
	for (dp = d, i = 0; i < n; dp++, i++) {
	    Nl[i].NL_NAME = dp->knm;
	}
	Nll = (int)((n + 1) * sizeof(struct NLIST_TYPE));
	Build_Nl = d;
}
#endif	/* defined(HASNLIST) */


/*
 * childx() - make child process exit (if possible)
 */

void
childx()
{
	static int at, sx;
	pid_t wpid;

	if (Cpid > 1) {

	/*
	 * First close the pipes to and from the child.  That should cause the
	 * child to exit.  Compute alarm time shares.
	 */
	    (void) closePipes();
	    if ((at = TmLimit / NCTSIGS) < TMLIMMIN)
		at = TMLIMMIN;
	/*
	 * Loop, waiting for the child to exit.  After the first pass, help
	 * the child exit by sending it signals.
	 */
	    for (sx = 0; sx < NCTSIGS; sx++) {
		if (setjmp(Jmp_buf)) {

		/*
		 * An alarm has rung.  Disable further alarms.
		 *
		 * If there are more signals to send, continue the signal loop.
		 *
		 * If the last signal has been sent, issue a warning (unless
		 * warninge have been suppressed) and exit the signal loop.
		 */
		    (void) alarm(0);
		    (void) signal(SIGALRM, SIG_DFL);
		    if (sx < (NCTSIGS - 1))
			continue;
		    if (!Fwarn)
			(void) fprintf(stderr,
			    "%s: WARNING -- child process %d may be hung.\n",
			    Pn, (int)Cpid);
		    break;
	        }
	    /*
	     * Send the next signal to the child process, after the first pass
	     * through the loop.
	     *
	     * Wrap the wait() with an alarm.
	     */
		if (sx)
		    (void) kill(Cpid, CtSigs[sx]);
		(void) signal(SIGALRM, handleint);
		(void) alarm(at);
		wpid = (pid_t) wait(NULL);
		(void) alarm(0);
		(void) signal(SIGALRM, SIG_DFL);
		if (wpid == Cpid)
		    break;
	    }
	    Cpid = 0;
	}
}


/*
 * closePipes() - close open pipe file descriptors
 */

static void
closePipes()
{
	int i;

	for (i = 0; i < 4; i++) {
	    if (Pipes[i] >= 0) {
		(void) close(Pipes[i]);
		Pipes[i] = -1;
	    }
	}
}


/*
 * compdev() - compare Devtp[] entries
 */

int
compdev(a1, a2)
	COMP_P *a1, *a2;
{
	struct l_dev **p1 = (struct l_dev **)a1;
	struct l_dev **p2 = (struct l_dev **)a2;

	if ((dev_t)((*p1)->rdev) < (dev_t)((*p2)->rdev))
	    return(-1);
	if ((dev_t)((*p1)->rdev) > (dev_t)((*p2)->rdev))
	    return(1);
	if ((INODETYPE)((*p1)->inode) < (INODETYPE)((*p2)->inode))
	    return(-1);
	if ((INODETYPE)((*p1)->inode) > (INODETYPE)((*p2)->inode))
	    return(1);
	return(strcmp((*p1)->name, (*p2)->name));
}


/*
 * doinchild() -- do a function in a child process
 */

static int
doinchild(fn, fp, rbuf, rbln)
	int (*fn)();			/* function to perform */
	char *fp;			/* function parameter */
	char *rbuf;			/* response buffer */
	int rbln;			/* response buffer length */
{
	int en, rv;

/*
 * Check reply buffer size.
 */
	if (!Fovhd && rbln > MAXPATHLEN) {
	    (void) fprintf(stderr,
		"%s: doinchild error; response buffer too large: %d\n",
		Pn, rbln);
	    Exit(1);
	}
/*
 * Set up to handle an alarm signal; handle an alarm signal; build
 * pipes for exchanging information with a child process; start the
 * child process; and perform functions in the child process.
 */
	if (!Fovhd) {
	    if (setjmp(Jmp_buf)) {

	    /*
	     * Process an alarm that has rung.
	     */
		(void) alarm(0);
		(void) signal(SIGALRM, SIG_DFL);
		(void) childx();
		errno = ETIMEDOUT;
		return(1);
	    } else if (!Cpid) {

	    /*
	     * Create pipes to exchange function information with a child
	     * process.
	     */
		if (pipe(Pipes) < 0 || pipe(&Pipes[2]) < 0) {
		    (void) fprintf(stderr, "%s: can't open pipes: %s\n",
			Pn, strerror(errno));
		    Exit(1);
		}
	    /*
	     * Fork a child to execute functions.
	     */
		if ((Cpid = fork()) == 0) {

		/*
		 * Begin the child process.
		 */

		    int r_al, r_rbln;
		    char r_arg[MAXPATHLEN+1], r_rbuf[MAXPATHLEN+1];
		    int (*r_fn)();
		/*
		 * Close sufficient open file descriptors except Pipes[0] and
		 * Pipes[3].
		 */

#if	defined(HAS_DUP2) && defined(HAS_CLOSEFROM)
		    int rc;

		    rc = dup2(Pipes[0], 0);
		    if (rc < 0) {
			(void) fprintf(stderr,
			    "%s: can't dup fd 0 for pipe: %s\n",
			    Pn, strerror(errno));
			Exit(1);
		    }
		    Pipes[0] = 0;
		    rc = dup2(Pipes[3], 1);
		    if (rc < 0) {
			(void) fprintf(stderr,
			    "%s: can't dup fd 3 for pipe: %s\n",
			    Pn, strerror(errno));
			Exit(1);
		    }
		    Pipes[3] = 1;
		    (void) closefrom(2);
		    Pipes[1] = -1;
		    Pipes[2] = -1;

#else	/* !defined(HAS_DUP2) && !defined(HAS_CLOSEFROM) */
		    int fd;

		    for (fd = 0; fd < MaxFd; fd++) {
			if (fd == Pipes[0] || fd == Pipes[3])
			    continue;
			(void) close(fd);
			if (fd == Pipes[1])
			    Pipes[1] = -1;
			else if (fd == Pipes[2])
			    Pipes[2] = -1;
		    }
		    if (Pipes[1] >= 0) {
			(void) close(Pipes[1]);
			Pipes[1] = -1;
		    }
		    if (Pipes[2] >= 0) {
			(void) close(Pipes[2]);
			Pipes[2] = -1;
		    }
#endif	/* defined(HAS_DUP2) && defined(HAS_CLOSEFROM) */

		/*
		 * Read function requests, process them, and return replies.
		 */
		    for (;;) {
			if (read(Pipes[0], (char *)&r_fn, sizeof(r_fn))
			    != (int)sizeof(r_fn)
			||  read(Pipes[0], (char *)&r_al, sizeof(int))
			    != (int)sizeof(int)
			||  r_al < 1
			||  r_al > (int)sizeof(r_arg)
			||  read(Pipes[0], r_arg, r_al) != r_al
			||  read(Pipes[0], (char *)&r_rbln, sizeof(r_rbln))
			    != (int)sizeof(r_rbln)
			||  r_rbln < 1 || r_rbln > (int)sizeof(r_rbuf))
			    break;
			rv = r_fn(r_arg, r_rbuf, r_rbln);
			en = errno;
			if (write(Pipes[3], (char *)&rv, sizeof(rv))
			    != sizeof(rv)
			||  write(Pipes[3], (char *)&en, sizeof(en))
			    != sizeof(en)
			||  write(Pipes[3], r_rbuf, r_rbln) != r_rbln)
			    break;
		    }
		    (void) _exit(0);
		}
	    /*
	     * Continue in the parent process to finish the setup.
	     */
		if (Cpid < 0) {
		    (void) fprintf(stderr, "%s: can't fork: %s\n",
			Pn, strerror(errno));
		    Exit(1);
		}
		(void) close(Pipes[0]);
		(void) close(Pipes[3]);
		Pipes[0] = Pipes[3] = -1;
	    }
	}
	if (!Fovhd) {
	    int len;

	/*
	 * Send a function to the child and wait for the response.
	 */
	    len  = strlen(fp) + 1;
	    (void) signal(SIGALRM, handleint);
	    (void) alarm(TmLimit);
	    if (write(Pipes[1], (char *)&fn, sizeof(fn)) != sizeof(fn)
	    ||  write(Pipes[1], (char *)&len, sizeof(len)) != sizeof(len)
	    ||  write(Pipes[1], fp, len) != len
	    ||  write(Pipes[1], (char *)&rbln, sizeof(rbln)) != sizeof(rbln)
	    ||  read(Pipes[2], (char *)&rv, sizeof(rv)) != sizeof(rv)
	    ||  read(Pipes[2], (char *)&en, sizeof(en)) != sizeof(en)
	    ||  read(Pipes[2], rbuf, rbln) != rbln) {
		(void) alarm(0);
		(void) signal(SIGALRM, SIG_DFL);
		(void) childx();
		errno = ECHILD;
		return(-1);
	    }
	} else {

	/*
	 * Do the operation directly -- not in a child.
	 */
	    (void) signal(SIGALRM, handleint);
	    (void) alarm(TmLimit);
	    rv = fn(fp, rbuf, rbln);
	    en = errno;
	}
/*
 * Function completed, response collected -- complete the operation.
 */
	(void) alarm(0);
	(void) signal(SIGALRM, SIG_DFL);
	errno = en;
	return(rv);
}


/*
 * dolstat() - do an lstat() function
 */

static int
dolstat(path, rbuf, rbln)
	char *path;			/* path */
	char *rbuf;			/* response buffer */
	int rbln;			/* response buffer length */

/* ARGSUSED */

{
	return(lstat(path, (struct stat *)rbuf));
}


/*
 * doreadlink() -- do a readlink() function
 */

static int
doreadlink(path, rbuf, rbln)
	char *path;			/* path */
	char *rbuf;			/* response buffer */
	int rbln;			/* response buffer length */
{
	return(readlink(path, rbuf, rbln));
}


/*
 * dostat() - do a stat() function
 */

static int
dostat(path, rbuf, rbln)
	char *path;			/* path */
	char *rbuf;			/* response buffer */
	int rbln;			/* response buffer length */

/* ARGSUSED */

{
	return(stat(path, (struct stat *)rbuf));
}


#if	defined(WILLDROPGID)
/*
 * dropgid() - drop setgid permission
 */

void
dropgid()
{
	if (!Setuidroot && Setgid) {
	    if (setgid(Mygid) < 0) {
		(void) fprintf(stderr, "%s: can't setgid(%d): %s\n",
		    Pn, (int)Mygid, strerror(errno));
		Exit(1);
	    }
	    Setgid = 0;
	}
}
#endif	/* defined(WILLDROPGID) */


/*
 * enter_dev_ch() - enter device characters in file structure
 */

void
enter_dev_ch(m)
	char *m;
{
	char *mp;

	if (!m || *m == '\0')
	    return;
	if (!(mp = mkstrcpy(m, (MALLOC_S *)NULL))) {
	    (void) fprintf(stderr, "%s: no more dev_ch space at PID %d: \n",
		Pn, Lp->pid);
	    safestrprt(m, stderr, 1);
	    Exit(1);
	}
	if (Lf->dev_ch)
	   (void) free((FREE_P *)Lf->dev_ch);
	Lf->dev_ch = mp;
}


/*
 * enter_IPstate() -- enter a TCP or UDP state
 */

void
enter_IPstate(ty, nm, nr)
	char *ty;			/* type -- TCP or UDP */
	char *nm;			/* state name (may be NULL) */
	int nr;				/* state number */
{

#if	defined(USE_LIB_PRINT_TCPTPI)
	TcpNstates = nr;
#else	/* !defined(USE_LIB_PRINT_TCPTPI) */

	int al, i, j, oc, nn, ns, off, tx;
	char *cp;
	MALLOC_S len;
/*
 * Check the type name and set the type index.
 */
	if (!ty) {
	    (void) fprintf(stderr,
		"%s: no type specified to enter_IPstate()\n", Pn);
	    Exit(1);
	}
	if (!strcmp(ty, "TCP"))
	    tx = 0;
	else if (!strcmp(ty, "UDP"))
	    tx = 1;
	else {
	    (void) fprintf(stderr, "%s: unknown type for enter_IPstate: %s\n",
		Pn, ty);
	    Exit(1);
	}
/*
 * If the name argument is NULL, reduce the allocated table to its minimum
 * size.
 */
	if (!nm) {
	    if (tx) {
		if (UdpSt) {
		    if (!UdpNstates) {
			(void) free((MALLOC_P *)UdpSt);
			UdpSt = (char **)NULL;
		    }
		    if (UdpNstates < UdpStAlloc) {
			len = (MALLOC_S)(UdpNstates * sizeof(char *));
			if (!(UdpSt = (char **)realloc((MALLOC_P *)UdpSt, len)))
			{
			    (void) fprintf(stderr,
				"%s: can't reduce UdpSt[]\n", Pn);
			    Exit(1);
			}
		    }
		    UdpStAlloc = UdpNstates;
		}
	    } else {
		if (TcpSt) {
		    if (!TcpNstates) {
			(void) free((MALLOC_P *)TcpSt);
			TcpSt = (char **)NULL;
		    }
		    if (TcpNstates < TcpStAlloc) {
			len = (MALLOC_S)(TcpNstates * sizeof(char *));
			if (!(TcpSt = (char **)realloc((MALLOC_P *)TcpSt, len)))
			{
			    (void) fprintf(stderr,
				"%s: can't reduce TcpSt[]\n", Pn);
			    Exit(1);
			}
		    }
		    TcpStAlloc = TcpNstates;
		}
	    }
	    return;
	}
/*
 * Check the name and number.
 */
	if ((len = (size_t)strlen(nm)) < 1) {
	    (void) fprintf(stderr,
		"%s: bad %s name (\"%s\"), number=%d\n", Pn, ty, nm, nr);
	    Exit(1);
	}
/*
 * Make a copy of the name.
 */
	if (!(cp = mkstrcpy(nm, (MALLOC_S *)NULL))) {
	    (void) fprintf(stderr,
		"%s: enter_IPstate(): no %s space for %s\n",
		Pn, ty, nm);
	    Exit(1);
	}
/*
 * Set the necessary offset for using nr as an index.  If it is
 * a new offset, adjust previous entries.
 */
	if ((nr < 0) && ((off = -nr) > (tx ? UdpStOff : TcpStOff))) {
	    if (tx ? UdpSt : TcpSt) {

	    /*
	     * A new, larger offset (smaller negative state number) could mean
	     * a previously allocated state table must be enlarged and its
	     * previous entries moved.
	     */
		oc = off - (tx ? UdpStOff : TcpStOff);
		al = tx ? UdpStAlloc : TcpStAlloc;
		ns = tx ? UdpNstates : TcpNstates;
		if ((nn = ns + oc) >= al) {
		    while ((nn + 5) > al) {
			al += TCPUDPALLOC;
		    }
		    len = (MALLOC_S)(al * sizeof(char *));
		    if (tx) {
			if (!(UdpSt = (char **)realloc((MALLOC_P *)UdpSt, len)))
			    goto no_IP_space;
			UdpStAlloc = al;
		    } else {
			if (!(TcpSt = (char **)realloc((MALLOC_P *)TcpSt, len)))
			    goto no_IP_space;
			TcpStAlloc = al;
		    }
		    for (i = 0, j = oc; i < oc; i++, j++) {
			if (tx) {
			    if (i < UdpNstates)
				UdpSt[j] = UdpSt[i];
			    UdpSt[i] = (char *)NULL;
			} else {
			    if (i < TcpNstates)
				TcpSt[j] = TcpSt[i];
			    TcpSt[i] = (char *)NULL;
			}
		    }
		    if (tx)
			UdpNstates += oc;
		    else
			TcpNstates += oc;
		}
	    }
	    if (tx)
		UdpStOff = off;
	    else
		TcpStOff = off;
	}
/*
 * Enter name as {Tc|Ud}pSt[nr + {Tc|Ud}pStOff].
 *
 * Allocate space, as required.
 */
	al = tx ? UdpStAlloc : TcpStAlloc;
	off = tx ? UdpStOff : TcpStOff;
	nn = nr + off + 1;
	if (nn > al) {
	    i = tx ? UdpNstates : TcpNstates;
	    while ((nn + 5) > al) {
		al += TCPUDPALLOC;
	    }
	    len = (MALLOC_S)(al * sizeof(char *));
	    if (tx) {
		if (UdpSt)
		    UdpSt = (char **)realloc((MALLOC_P *)UdpSt, len);
		else
		    UdpSt = (char **)malloc(len);
		if (!UdpSt) {

no_IP_space:

		    (void) fprintf(stderr, "%s: no %s state space\n", Pn, ty);
		    Exit(1);
		}
		UdpNstates = nn;
		UdpStAlloc = al;
	    } else {
		if (TcpSt)
		    TcpSt = (char **)realloc((MALLOC_P *)TcpSt, len);
		else
		    TcpSt = (char **)malloc(len);
		if (!TcpSt)
		    goto no_IP_space;
		TcpNstates = nn;
		TcpStAlloc = al;
	    }
	    while (i < al) {
		if (tx)
		    UdpSt[i] = (char *)NULL;
		else
		    TcpSt[i] = (char *)NULL;
		i++;
	    }
	} else {
	    if (tx) {
		if (nn > UdpNstates)
		    UdpNstates = nn;
	    } else {
		if (nn > TcpNstates)
		    TcpNstates = nn;
	    }
	}
	if (tx) {
	    if (UdpSt[nr + UdpStOff]) {

dup_IP_state:

		(void) fprintf(stderr,
		    "%s: duplicate %s state %d (already %s): %s\n",
		    Pn, ty, nr,
		    tx ? UdpSt[nr + UdpStOff] : TcpSt[nr + TcpStOff],
		    nm);
	 	Exit(1);
	    }
	    UdpSt[nr + UdpStOff] = cp;
	} else {
	    if (TcpSt[nr + TcpStOff])
		goto dup_IP_state;
	    TcpSt[nr + TcpStOff] = cp;
	}
#endif	/* defined(USE_LIB_PRINT_TCPTPI) */

}


/*
 * enter_nm() - enter name in local file structure
 */

void
enter_nm(m)
	char *m;
{
	char *mp;

	if (!m || *m == '\0')
	    return;
	if (!(mp = mkstrcpy(m, (MALLOC_S *)NULL))) {
	    (void) fprintf(stderr, "%s: no more nm space at PID %d for: ",
		Pn, Lp->pid);
	    safestrprt(m, stderr, 1);
	    Exit(1);
	}
	if (Lf->nm)
	    (void) free((FREE_P *)Lf->nm);
	Lf->nm = mp;
}


/*
 * Exit() - do a clean exit()
 */

void
Exit(xv)
	int xv;				/* exit() value */
{
	(void) childx();

#if	defined(HASDCACHE)
	if (DCrebuilt && !Fwarn)
	    (void) fprintf(stderr, "%s: WARNING: %s was updated.\n",
		Pn, DCpath[DCpathX]);
#endif	/* defined(HASDCACHE) */

	exit(xv);
}


#if	defined(HASNLIST)
/*
 * get_Nl_value() - get Nl value for nickname
 */

int
get_Nl_value(nn, d, v)
	char *nn;			/* nickname of requested entry */
	struct drive_Nl *d;		/* drive_Nl table that built Nl
					 * (if NULL, use Build_Nl) */
	KA_T *v;			/* returned value (if NULL,
					 * return nothing) */
{
	int i;

	if (!Nl || !Nll)
	    return(-1);
	if (!d)
	    d = Build_Nl;
	for (i = 0; d->nn; d++, i++) {
	    if (strcmp(d->nn, nn) == 0) {
		if (v)
		    *v = (KA_T)Nl[i].n_value;
		return(i);
	    }
	}
	return(-1);
}
#endif	/* defined(HASNLIST) */


/*
 * handleint() - handle an interrupt
 */

#if	defined(HASINTSIGNAL)
static int
#else
static void
#endif

/* ARGSUSED */

handleint(sig)
	int sig;
{
	longjmp(Jmp_buf, 1);
}


/*
 * hashbyname() - hash by name
 */

int
hashbyname(nm, mod)
	char *nm;			/* pointer to NUL-terminated name */
	int mod;			/* hash modulus */
{
	int i, j;

	for (i = j = 0; *nm; nm++) {
	    i ^= (int)*nm << j;
	    if (++j > 7)
		j = 0;
	}
	return(((int)(i * 31415)) & (mod - 1));
}


/*
 * is_nw_addr() - is this network address selected?
 */

int
is_nw_addr(ia, p, af)
	unsigned char *ia;		/* Internet address */
	int p;				/* port */
	int af;				/* address family -- e.g., AF_INET,
					 * AF_INET6 */
{
	struct nwad *n;

	if (!(n = Nwad))
	    return(0);
	for (; n; n = n->next) {
	    if (n->proto) {
		if (strcasecmp(n->proto, Lf->iproto) != 0)
		    continue;
	    }
	    if (af && n->af && af != n->af)
		continue;

#if	defined(HASIPv6)
	    if (af == AF_INET6) {
		if (n->a[15] || n->a[14] || n->a[13] || n->a[12]
		||  n->a[11] || n->a[10] || n->a[9]  || n->a[8]
		||  n->a[7]  || n->a[6]  || n->a[5]  || n->a[4]
		||  n->a[3]  || n->a[2]  || n->a[1]  || n->a[0]) {
		    if (ia[15] != n->a[15] || ia[14] != n->a[14]
		    ||  ia[13] != n->a[13] || ia[12] != n->a[12]
		    ||  ia[11] != n->a[11] || ia[10] != n->a[10]
		    ||  ia[9]  != n->a[9]  || ia[8]  != n->a[8]
		    ||  ia[7]  != n->a[7]  || ia[6]  != n->a[6]
		    ||  ia[5]  != n->a[5]  || ia[4]  != n->a[4]
		    ||  ia[3]  != n->a[3]  || ia[2]  != n->a[2]
		    ||  ia[1]  != n->a[1]  || ia[0]  != n->a[0])
			continue;
		}
	    } else if (af == AF_INET)
#endif	/* defined(HASIPv6) */

	    {
		if (n->a[3] || n->a[2] || n->a[1] || n->a[0]) {
		    if (ia[3] != n->a[3] || ia[2] != n->a[2]
		    ||  ia[1] != n->a[1] || ia[0] != n->a[0])
			continue;
		}
	    }

#if	defined(HASIPv6)
	    else
		continue;
#endif	/* defined(HASIPv6) */

	    if (n->sport == -1 || (p >= n->sport && p <= n->eport)) {
		n->f = 1;
		return(1);
	    }
	}
	return(0);
}


/*
 * mkstrcpy() - make a string copy in malloc()'d space
 *
 * return: copy pointer
 *	   copy length (optional)
 */

char *
mkstrcpy(src, rlp)
	char *src;			/* source */
	MALLOC_S *rlp;			/* returned length pointer (optional)
					 * The returned length is an strlen()
					 * equivalent */
{
	MALLOC_S len;
	char *ns;

	len = (MALLOC_S)(src ? strlen(src) : 0);
	ns = (char *)malloc(len + 1);
	if (ns) {
	    if (src)
		(void) snpf(ns, len + 1, "%s", src);
	    else
		*ns = '\0';
	}
	if (rlp)
	    *rlp = len;
	return(ns);
}


/*
 * mkstrcat() - make a catenated copy of up to three strings under optional
 *		string-by-string count control
 *
 * return: copy pointer
 *	   copy string length (optional)
 */

char *
mkstrcat(s1, l1, s2, l2, s3, l3, clp)
	char *s1;			/* source string 1 */
	int l1;				/* length of string 1 (-1 if none) */
	char *s2;			/* source string 2 */
	int l2;				/* length of string 2 (-1 if none) */
	char *s3;			/* source string 3 (optional) */
	int l3	;			/* length of string 3 (-1 if none) */
	MALLOC_S *clp;			/* pointer to return of copy length
					 * (optional) */
{
	MALLOC_S cl, len1, len2, len3;
	char *cp;

	if (s1)
	    len1 = (MALLOC_S)((l1 >= 0) ? l1 : strlen(s1));
	else
	    len1 = (MALLOC_S)0;
	if (s2)
	    len2 = (MALLOC_S)((l2 >= 0) ? l2 : strlen(s2));
	else
	    len2 = (MALLOC_S)0;
	if (s3)
	    len3 = (MALLOC_S)((l3 >= 0) ? l3 : strlen(s3));
	else
	    len3 = (MALLOC_S)0;
	cl = len1 + len2 + len3;
	if ((cp = (char *)malloc(cl + 1))) {
	    char *tp = cp;

	    if (s1 && len1) {
		(void) strncpy(tp, s1, len1);
		tp += len1;
	    }
	    if (s2 && len2) {
		(void) strncpy(tp, s2, len2);
		tp += len2;
	    }
	    if (s3 && len3) {
		(void) strncpy(tp, s3, len3);
		tp += len3;
	    }
	    *tp = '\0';
	}
	if (clp)
	    *clp = cl;
	return(cp);
}


/*
 * is_readable() -- is file readable
 */

int
is_readable(path, msg)
	char *path;			/* file path */
	int msg;			/* issue warning message if 1 */
{
	if (access(path, R_OK) < 0) {
	    if (!Fwarn && msg == 1)
		(void) fprintf(stderr, ACCESSERRFMT, Pn, path, strerror(errno));
	    return(0);
	}
	return(1);
}


/*
 * lstatsafely() - lstat path safely (i. e., with timeout)
 */

int
lstatsafely(path, buf)
	char *path;			/* file path */
	struct stat *buf;		/* stat buffer address */
{
	if (Fblock) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: avoiding stat(%s): -b was specified.\n",
		    Pn, path);
	    errno = EWOULDBLOCK;
	    return(1);
	}
	return(doinchild(dolstat, path, (char *)buf, sizeof(struct stat)));
}


/*
 * Readlink() - read and interpret file system symbolic links
 */

char *
Readlink(arg)
	char *arg;			/* argument to be interpreted */
{
	char abuf[MAXPATHLEN+1];
	int alen;
	char *ap;
	char *argp1, *argp2;
	int i, len, llen, slen;
	char lbuf[MAXPATHLEN+1];
	static char *op = (char *)NULL;
	static int ss = 0;
	char *s1;
	static char **stk = (char **)NULL;
	static int sx = 0;
	char tbuf[MAXPATHLEN+1];
/*
 * See if avoiding kernel blocks.
 */
	if (Fblock) {
	    if (!Fwarn) {
		(void) fprintf(stderr, "%s: avoiding readlink(", Pn);
		safestrprt(arg, stderr, 0);
		(void) fprintf(stderr, "): -b was specified.\n");
	    }
	    op = (char *)NULL;
	    return(arg);
	}
/*
 * Save the original path.
 */
	if (!op)
	    op = arg;
/*
 * Evaluate each component of the argument for a symbolic link.
 */
	for (alen = 0, ap = abuf, argp1 = argp2 = arg; *argp2; argp1 = argp2 ) {
	    for (argp2 = argp1 + 1; *argp2 && *argp2 != '/'; argp2++)
		;
	    if ((len = argp2 - arg) >= (int)sizeof(tbuf)) {

path_too_long:
		if (!Fwarn) {
		    (void) fprintf(stderr,
			"%s: readlink() path too long: ", Pn);
		    safestrprt(op ? op : arg, stderr, 1);
		}
		op = (char *)NULL;
		return((char *)NULL);
	    }
	    (void) strncpy(tbuf, arg, len);
	    tbuf[len] = '\0';
	/*
	 * Dereference a symbolic link.
	 */
	    if ((llen=doinchild(doreadlink,tbuf,lbuf,sizeof(lbuf) - 1)) >= 0) {

	    /*
	     * If the link is a new absolute path, replace
	     * the previous assembly with it.
	     */
		if (lbuf[0] == '/') {
		    (void) strncpy(abuf, lbuf, llen);
		    ap = &abuf[llen];
		    *ap = '\0';
		    alen = llen;
		    continue;
		}
		lbuf[llen] = '\0';
		s1 = lbuf;
	    } else {
		llen = argp2 - argp1;
		s1 = argp1;
	    }
	/*
	 * Make sure two components are separated by a `/'.
	 *
	 * If the first component is not a link, don't force
	 * a leading '/'.
	 *
	 * If the first component is a link and the source of
	 * the link has a leading '/', force a leading '/'.
	 */
	    if (*s1 == '/')
		slen = 1;
	    else {
		if (alen > 0) {

		/*
		 * This is not the first component.
		 */
		    if (abuf[alen - 1] == '/')
			slen = 1;
		    else
			slen = 2;
		} else {

		/*
		 * This is the first component.
		 */
		    if (s1 == lbuf && tbuf[0] == '/')
			slen = 2;
		    else
			slen = 1;
		}
	    }
	/*
	 * Add to the path assembly.
	 */
	    if ((alen + llen + slen) >= (int)sizeof(abuf))
		goto path_too_long;
	    if (slen == 2)
		*ap++ = '/';
	    (void) strncpy(ap, s1, llen);
	    ap += llen;
	    *ap = '\0';
	    alen += (llen + slen - 1);
	}
/*
 * If the assembled path and argument are the same, free all but the
 * last string in the stack, and return the argument.
 */
	if (strcmp(arg, abuf) == 0) {
	    for (i = 0; i < sx; i++) {
		if (i < (sx - 1))
		    (void) free((FREE_P *)stk[i]);
		stk[i] = (char *)NULL;
	    }
	    sx = 0;
	    op = (char *)NULL;
	    return(arg);
	}
/*
 * If the assembled path and argument are different, add it to the
 * string stack, then Readlink() it.
 */
	if (!(s1 = mkstrcpy(abuf, (MALLOC_S *)NULL))) {

no_readlink_space:

	    (void) fprintf(stderr, "%s: no Readlink string space for ", Pn);
	    safestrprt(abuf, stderr, 1);
	    Exit(1);
	}
	if (sx >= MAXSYMLINKS) {

	/*
	 * If there are too many symbolic links, report an error, clear
	 * the stack, and return no path.
	 */
	    if (!Fwarn) {
		(void) fprintf(stderr,
		    "%s: too many (> %d) symbolic links in readlink() path: ",
			Pn, MAXSYMLINKS);
		safestrprt(op ? op : arg, stderr, 1);
	    }
	    for (i = 0; i < sx; i++) {
		(void) free((FREE_P *)stk[i]);
		stk[i] = (char *)NULL;
	    }
	    (void) free((FREE_P *)stk);
	    stk = (char **)NULL;
	    ss = sx = 0;
	    op = (char *)NULL;
	    return((char *)NULL);
	}
	if (++sx > ss) {
	    if (!stk)
		stk = (char **)malloc((MALLOC_S)(sizeof(char *) * sx));
	    else
		stk = (char **)realloc((MALLOC_P *)stk,
					(MALLOC_S)(sizeof(char *) * sx));
	    if (!stk)
		goto no_readlink_space;
	    ss = sx;
	}
	stk[sx - 1] = s1;
	return(Readlink(s1));
}


#if	defined(HASSTREAMS)
/*
 * readstdata() - read stream's stdata structure
 */

int
readstdata(addr, buf)
	KA_T addr;			/* stdata address in kernel*/
	struct stdata *buf;		/* buffer addess */
{
	if (!addr
	||  kread(addr, (char *)buf, sizeof(struct stdata))) {
	    (void) snpf(Namech, Namechl, "no stream data in %s",
		print_kptr(addr, (char *)NULL, 0));
	    return(1);
	}
	return(0);
}


/*
 * readsthead() - read stream head
 */

int
readsthead(addr, buf)
	KA_T addr;			/* starting queue pointer in kernel */
	struct queue *buf;		/* buffer for queue head */
{
	KA_T qp;

	if (!addr) {
	    (void) snpf(Namech, Namechl, "no stream queue head");
	    return(1);
	}
	for (qp = addr; qp; qp = (KA_T)buf->q_next) {
	    if (kread(qp, (char *)buf, sizeof(struct queue))) {
		(void) snpf(Namech, Namechl, "bad stream queue link at %s",
		    print_kptr(qp, (char *)NULL, 0));
		return(1);
	    }
	}
	return(0);
}


/*
 * readstidnm() - read stream module ID name
 */

int
readstidnm(addr, buf, len)
	KA_T addr;			/* module ID name address in kernel */
	char *buf;			/* receiving buffer address */
	READLEN_T len;			/* buffer length */
{
	if (!addr || kread(addr, buf, len)) {
	    (void) snpf(Namech, Namechl, "can't read module ID name from %s",
		print_kptr(addr, (char *)NULL, 0));
	    return(1);
	}
	return(0);
}


/*
 * readstmin() - read stream's module info
 */

int
readstmin(addr, buf)
	KA_T addr;			/* module info address in kernel */
	struct module_info *buf;	/* receiving buffer address */
{
	if (!addr || kread(addr, (char *)buf, sizeof(struct module_info))) {
	    (void) snpf(Namech, Namechl, "can't read module info from %s",
		print_kptr(addr, (char *)NULL, 0));
	    return(1);
	}
	return(0);
}


/*
 * readstqinit() - read stream's queue information structure
 */

int
readstqinit(addr, buf)
	KA_T addr;			/* queue info address in kernel */
	struct qinit *buf;		/* receiving buffer address */
{
	if (!addr || kread(addr, (char *)buf, sizeof(struct qinit))) {
	    (void) snpf(Namech, Namechl, "can't read queue info from %s",
		print_kptr(addr, (char *)NULL, 0));
	    return(1);
	}
	return(0);
}
#endif	/* HASSTREAMS */


/*
 * safepup() - safely print an unprintable character -- i.e., print it in a
 *	       printable form
 *
 * return: char * to printable equivalent
 *	   cl = strlen(printable equivalent)
 */

char *
safepup(c, cl)
	unsigned int c;			/* unprintable (i.e., !isprint())
					 * character  and '\\' */
	int *cl;			/* returned printable strlen -- NULL if
					 * no return needed */
{
	int len;
	char *rp;
	static char up[8];

	if (c < 0x20) {
	    switch (c) {
	    case '\b':
		rp = "\\b";
		break;
	    case '\f':
		rp = "\\f";
		break;
	    case '\n':
		rp = "\\n";
		break;
	    case '\r':
		rp = "\\r";
		break;
	    case '\t':
		rp = "\\t";
		break;
	    default:
		(void) snpf(up, sizeof(up), "^%c", c + 0x40);
		rp = up;
	    }
	    len = 2;
	} else if (c == 0xff) {
	    rp = "^?";
	    len = 2;
	} else if (c == '\\') {
	    rp = "\\\\";
	    len = 2;
	} else {
	    (void) snpf(up, sizeof(up), "\\x%02x", (int)(c & 0xff));
	    rp = up;
	    len = 4;
	}
	if (cl)
	    *cl = len;
	return(rp);
}


/*
 * safestrlen() - calculate a "safe" string length -- i.e., compute space for
 *		  non-printable characters when printed in a printable form
 */

int
safestrlen(sp, flags)
	char *sp;			/* string pointer */
	int flags;			/* flags:
					 *   bit 0: 0 (0) = no NL
					 *	    1 (1) = add trailing NL
					 *	 1: 0 (0) = ' ' printable
					 *	    1 (2) = ' ' not printable
					 */
{
	char c;
	int len = 0;

	c = (flags & 2) ? ' ' : '\0';
	if (sp) {
	    for (; *sp; sp++) {
		if (!isprint((unsigned char)*sp)
		||  (*sp == '\\') || (*sp == c))
		{
		    if ((*sp < 0x20) || ((unsigned char)*sp == 0xff)
		    ||  (*sp == '\\'))
			len += 2;		/* length of \. or ^. form */
		    else
			len += 4;		/* length of "\x%02x" printf */
		} else
		    len++;
	    }
	}
	return(len);
}


/*
 * safestrprt() - print a string "safely" to the indicated stream -- i.e.,
 *		  print unprintable characters in a printable form
 */

void
safestrprt(sp, fs, flags)
	char *sp;			/* string to print pointer pointer */
	FILE *fs;			/* destination stream -- e.g., stderr
					 * or stdout */
	int flags;			/* flags:
					 *   bit 0: 0 (0) = no NL
					 *	    1 (1) = add trailing NL
					 *	 1: 0 (0) = ' ' printable
					 *	    1 (2) = ' ' not printable
					 *	 2: 0 (0) = print string as is
					 *	    1 (4) = surround string
					 *		    with '"'
					 *	 4: 0 (0) = print ending '\n'
					 *	    1 (8) = don't print ending
					 *		    '\n'
					 */
{
	char c;
	int lnc, lnt, sl;

#if	defined(HASWIDECHAR)
	wchar_t w;
	int wcmx = MB_CUR_MAX;
#else	/* !defined(HASWIDECHAR) */
	static int wcmx = 1;
#endif	/* defined(HASWIDECHAR) */

	c = (flags & 2) ? ' ' : '\0';
	if (flags & 4)
	    putc('"', fs);
	if (sp) {
	    for (sl = strlen(sp); *sp; sl -= lnc, sp += lnc) {

#if	defined(HASWIDECHAR)
		if (wcmx > 1) {
		    lnc = mblen(sp, sl);
		    if (lnc > 1) {
			if ((mbtowc(&w, sp, sl) == lnc) && iswprint(w)) {
			    for (lnt = 0; lnt < lnc; lnt++) {
				putc((int)*(sp + lnt), fs);
			    }
			} else {
			    for (lnt = 0; lnt < lnc; lnt++) {
			        fputs(safepup((unsigned int)*(sp + lnt),
					      (int *)NULL), fs);
			    }
			}
			continue;
		    } else
			lnc = 1;
		} else
		    lnc = 1;
#else	/* !defined(HASWIDECHAR) */
		lnc = 1;
#endif	/* defined(HASWIDECHAR) */

		if ((*sp != '\\') && isprint((unsigned char)*sp) && *sp != c)
		    putc((int)(*sp & 0xff), fs);
		else {
		    if ((flags & 8) && (*sp == '\n') && !*(sp + 1))
			break;
		    fputs(safepup((unsigned int)*sp, (int *)NULL), fs);
		}
	    }
	}
	if (flags & 4)
	    putc('"', fs);
	if (flags & 1)
	    putc('\n', fs);
}


/*
 * safestrprtn() - print a specified number of characters from a string
 *		   "safely" to the indicated stream
 */

void
safestrprtn(sp, len, fs, flags)
	char *sp;			/* string to print pointer pointer */
	int len;			/* safe number of characters to
					 * print */
	FILE *fs;			/* destination stream -- e.g., stderr
					 * or stdout */
	int flags;			/* flags:
					 *   bit 0: 0 (0) = no NL
					 *	    1 (1) = add trailing NL
					 *	 1: 0 (0) = ' ' printable
					 *	    1 (2) = ' ' not printable
					 *	 2: 0 (0) = print string as is
					 *	    1 (4) = surround string
					 *		    with '"'
					 *	 4: 0 (0) = print ending '\n'
					 *	    1 (8) = don't print ending
					 *		    '\n'
					 */
{
	char c, *up;
	int cl, i;

	if (flags & 4)
	    putc('"', fs);
	if (sp) {
	    c = (flags & 2) ? ' ' : '\0';
	    for (i = 0; i < len && *sp; sp++) {
		if ((*sp != '\\') && isprint((unsigned char)*sp) && *sp != c) {
		    putc((int)(*sp & 0xff), fs);
		    i++;
		} else {
		    if ((flags & 8) && (*sp == '\n') && !*(sp + 1))
			break;
		    up = safepup((unsigned int)*sp, &cl);
		    if ((i + cl) > len)
			break;
		    fputs(up, fs);
		    i += cl;
		}
	    }
	} else
	    i = 0;
	for (; i < len; i++)
	    putc(' ', fs);
	if (flags & 4)
	    putc('"', fs);
	if (flags & 1)
	    putc('\n', fs);
}


/*
 * statsafely() - stat path safely (i. e., with timeout)
 */

int
statsafely(path, buf)
	char *path;			/* file path */
	struct stat *buf;		/* stat buffer address */
{
	if (Fblock) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: avoiding stat(%s): -b was specified.\n",
		    Pn, path);
	    errno = EWOULDBLOCK;
	    return(1);
	}
	return(doinchild(dostat, path, (char *)buf, sizeof(struct stat)));
}


/*
 * stkdir() - stack directory name
 */

void
stkdir(p)
	char *p;		/* directory path */
{
	MALLOC_S len;
/*
 * Provide adequate space for directory stack pointers.
 */
	if (Dstkx >= Dstkn) {
	    Dstkn += 128;
	    len = (MALLOC_S)(Dstkn * sizeof(char *));
	    if (!Dstk)
		Dstk = (char **)malloc(len);
	    else
		Dstk = (char **)realloc((MALLOC_P *)Dstk, len);
	    if (!Dstk) {
		(void) fprintf(stderr,
		    "%s: no space for directory stack at: ", Pn);
		safestrprt(p, stderr, 1);
		Exit(1);
	    }
	}
/*
 * Allocate space for the name, copy it there and put its pointer on the stack.
 */
	if (!(Dstk[Dstkx] = mkstrcpy(p, (MALLOC_S *)NULL))) {
	    (void) fprintf(stderr, "%s: no space for: ", Pn);
	    safestrprt(p, stderr, 1);
	    Exit(1);
	}
	Dstkx++;
}


/*
 * x2dev() - convert hexadecimal ASCII string to device number
 */

char *
x2dev(s, d)
	char *s;			/* ASCII string */
	dev_t *d;			/* device receptacle */
{
	char *cp, *cp1;
	int n;
	dev_t r;

/*
 * Skip an optional leading 0x.  Count the number of hex digits up to the end
 * of the string, or to a space, or to a comma.  Return an error if an unknown
 * character is encountered.  If the count is larger than (2 * sizeof(dev_t))
 * -- e.g., because of sign extension -- ignore excess leading hex 0xf digits,
 * but return an error if an excess leading digit isn't 0xf.
 */
	if  (strncasecmp(s, "0x", 2) == 0)
		s += 2;
	for (cp = s, n = 0; *cp; cp++, n++) {
	    if (isdigit((unsigned char)*cp))
		continue;
	    if ((unsigned char)*cp >= 'a' && (unsigned char)*cp <= 'f')
		continue;
	    if ((unsigned char)*cp >= 'A' && (unsigned char)*cp <= 'F')
		continue;
	    if (*cp == ' ' || *cp == ',')
		break;
	    return((char *)NULL);
	}
	if (!n)
	    return((char *)NULL);
	if (n > (2 * (int)sizeof(dev_t))) {
	    cp1 = s;
	    s += (n - (2 * sizeof(dev_t)));
	    while (cp1 < s) {
		if (*cp1 != 'f' && *cp1 != 'F')
		    return((char *)NULL);
		cp1++;
	    }
	}
/*
 * Assemble the validated hex digits of the device number, starting at a point
 * in the string relevant to sizeof(dev_t).
 */
	for (r = 0; s < cp; s++) {
	    r = r << 4;
	    if (isdigit((unsigned char)*s))
		r |= (unsigned char)(*s - '0') & 0xf;
	    else {
		if (isupper((unsigned char)*s))
		    r |= ((unsigned char)(*s - 'A') + 10) & 0xf;
		else
		    r |= ((unsigned char)(*s - 'a') + 10) & 0xf;
	    }
	}
	*d = r;
	return(s);
}
