/*
 * 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.19 95/09/19 10:38:58 abe Exp $";
#endif


#include "lsof.h"

_PROTOTYPE(static int dolstat,(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
_PROTOTYPE(static void handleint,(int sig));
#endif


static jmp_buf Jmp_buf;			/* jump buffer */


#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, Mygid, strerror(errno));
			exit(1);
		}
		Setgid = 0;
	}
}
#endif	/* defined(WILLDROPGID) */


/*
 * 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);
}


/*
 * 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 ((long)((*p1)->rdev) < (long)((*p2)->rdev))
		return(-1);
	if ((long)((*p1)->rdev) > (long)((*p2)->rdev))
		return(1);
	if ((long)((*p1)->inode) < (long)((*p2)->inode))
		return(-1);
	if ((long)((*p1)->inode) > (long)((*p2)->inode))
		return(1);
	return(0);
}


/*
 * 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;
	static pid_t cpid;
	int p[2];
	static int wf;
/*
 * Set up to handle an alarm signal.
 *
 * Handle the signal.
 */
	cpid = wf = 0;
	if (setjmp(Jmp_buf)) {
	    (void) alarm(0);
	    (void) signal(SIGALRM, SIG_DFL);
	    if (!Fovhd) {
		if (wf) {
		    if (cpid && !Fwarn)
			(void) fprintf(stderr,
			    "%s: WARNING -- child process %d may be hung.\n",
			    Pn, cpid);
		} else if (cpid) {
		    (void) kill(cpid, SIGINT);
		    (void) kill(cpid, SIGKILL);
		    wf++;
		    (void) signal(SIGALRM, handleint);
		    (void) alarm(TmLimit);
		    (void) wait(NULL);
		    (void) alarm(0);
		    (void) signal(SIGALRM, SIG_DFL);
		}
	    }
	    errno = ETIMEDOUT;
	    return(-1);
	}
	if (!Fovhd) {

	/*
	 * Create a pipe from which to read the child function's response.
	 */
	    if (pipe(p) < 0) {
		(void) fprintf(stderr, "%s: can't open pipe: %s\n",
		    Pn, strerror(errno));
		exit(1);
	    }
	/*
	 * Fork a child to execute the function.
	 */
	    if ((cpid = fork()) == 0) {
		(void) close(p[0]);
		rv = fn(fp, rbuf, rbln);
		en = errno;
		write(p[1], (char *)&rv, sizeof(rv));
		write(p[1], (char *)&en, sizeof(en));
		write(p[1], rbuf, rbln);
		(void) close(p[1]);
		_exit(0);
	    }
	    if (cpid < 0) {
		(void) fprintf(stderr, "%s: can't fork: %s\n",
		    Pn, strerror(errno));
		exit(1);
	    }
	/*
	 * Wait for the response in the parent.
	 */
	    (void) close(p[1]);
	    (void) signal(SIGALRM, handleint);
	    (void) alarm(TmLimit);
	    if (read(p[0], (char *)&rv, sizeof(rv)) != sizeof(rv)
	    ||  read(p[0], (char *)&en, sizeof(en)) != sizeof(en)
	    ||  read(p[0], rbuf, rbln) != rbln) {

	    /*
	     * Cannot complete the read from the child.
	     */
		(void) kill(cpid, SIGINT);
		(void) kill(cpid, SIGKILL);
		(void) close(p[0]);
		wf++;
		(void) alarm(TmLimit);
		(void) wait(NULL);
		(void) alarm(0);
		(void) signal(SIGALRM, SIG_DFL);
		errno = EINTR;
		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);
	if (!Fovhd) {
	    (void) close(p[0]);
	    wf++;
	    (void) alarm(TmLimit);
	    (void) wait(NULL);
	    (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));
}


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

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

	if (m == NULL || *m == '\0')
		return;
	len = strlen(m) + 1;
	if ((mp = (char *)malloc(len)) == NULL) {
		(void) fprintf(stderr, "%s: no more dev_ch space at PID %d\n",
			Pn, Lp->pid);
		exit(1);
	}
	(void) strcpy(mp, m);
	if (Lf->dev_ch)
		(void) free((FREE_P *)Lf->dev_ch);
	Lf->dev_ch = mp;
}


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

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

	if (m == NULL || *m == '\0')
		return;
	len = strlen(m) + 1;
	if ((mp = (char *)malloc(len)) == NULL) {
		(void) fprintf(stderr, "%s: no more nm space at PID %d\n",
			Pn, Lp->pid);
		exit(1);
	}
	(void) strcpy(mp, m);
	if (Lf->nm)
		(void) free((FREE_P *)Lf->nm);
	Lf->nm = mp;
}


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

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

/* ARGSUSED */

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


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

int
is_nw_addr(ia, p)
	struct in_addr *ia;		/* Internet address */
	int p;				/* port */
{
	struct nwad *n;
	unsigned char *u;

	if ((n = Nwad) == NULL)
		return(0);
	u = (unsigned char *)ia;
	for (; n; n = n->next) {
		if (n->proto) {
			if (strcasecmp(n->proto, Lf->iproto) != 0)
				continue;
		}
		if (n->a[0] != 0 || n->a[1] != 0
		||  n->a[2] != 0 || n->a[3] != 0) {
			if (u[3] != n->a[3] || u[2] != n->a[2]
			||  u[1] != n->a[1] || u[0] != n->a[0])
				continue;
		}
		if (n->port == -1 || p == n->port)
			return(1);
	}
	return(0);
}


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

char *
Readlink(arg)
	char *arg;			/* argument to be interpreted */
{
	char abuf[MAXPATHLEN];
	int alen;
	char *ap;
	char *argp1, *argp2;
	int i, len, llen, slen;
	char lbuf[MAXPATHLEN];
	static int ss = 0;
	char *s1;
	static char **stk = NULL;
	static int sx = 0;
	char tbuf[MAXPATHLEN];
/*
 * See if avoiding kernel blocks.
 */
	if (Fblock) {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: avoiding readlink(%s): -b was specified.\n",
			Pn, arg);
		return(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) >= MAXPATHLEN) {

path_too_long:
			if (!Fwarn) {
				(void) fprintf(stderr,
					"%s: path too long: %s\n", Pn, arg);
			}
			return(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) >= 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] = NULL;
		}
		sx = 0;
		return(arg);
	}
/*
 * If the assembled path and argument are different, add it to the
 * string stack, then Readlink() it.
 */
	if ((s1 = (char *)malloc((MALLOC_S)(alen + 1))) == NULL) {

no_readlink_space:

		(void) fprintf(stderr,
			"%s: no Readlink string space for %s\n", Pn, abuf);
		exit(1);
	}
	(void) strcpy(s1, abuf);
	if (++sx > ss) {
		if (stk == NULL)
			stk = (char **)malloc(sizeof(char *) * sx);
		else
			stk = (char **)realloc(stk, sizeof(char *) * sx);
		if (stk == NULL)
			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)
	struct stdata *addr;		/* stdata address in kernel*/
	struct stdata *buf;		/* buffer addess */
{
	if (addr == NULL
	||  kread((KA_T)addr, (char *)buf, sizeof(struct stdata))) {
		(void) sprintf(Namech, "no stream data in %#x\n", addr);
		return(1);
	}
	return(0);
}


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

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

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


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

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


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

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


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

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


/*
 * 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(dolstat, path, (char *)buf, sizeof(struct stat)));
}


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

char *
x2dev(s, d)
	char *s;			/* ASCII string */
	dev_t *d;			/* device receptacle */
{
	char c;
	short n, nl;
	dev_t r;
	unsigned char v;

	if  (strncasecmp(s, "0x", 2) == 0)
		s += 2;
	for (nl = (sizeof(dev_t) * 2), n = r = v = 0; *s; s++) {
		if (isdigit(*s))
			v = (unsigned char)(*s - '0');
		else if (isalpha(*s) && (c = toupper(*s)) >= 'A' && c <= 'F')
			v = (unsigned char)(c - 'A' + 10);
		else
			break;
		if (++n > nl)
			return(NULL);
		r = (r << 4) | (v & 0xf);
	}
	if (n && ( ! *s || isspace(*s) || *s == ',')) {
		*d = r;
		return(s);
	}
	return(NULL);
}
