/*
 * dproc.c - AIX process access 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: dproc.c,v 1.15 96/03/08 16:17:27 abe Exp $";
#endif


#include "lsof.h"


_PROTOTYPE(static void get_kernel_access,(void));
_PROTOTYPE(static struct le *getle,(long a, long sid, char **err));

#if	_AIXV>=4110
_PROTOTYPE(static void getlenm,(struct le *le));
#endif	/* _AIXV>=4110 */

_PROTOTYPE(static void process_text,(unsigned long sid));


/*
 * Local static values
 */

static int Np = 0;			/* number of processes */
static struct procinfo *P = NULL;	/* the process table */
static struct user *Up;			/* user structure */

#if	_AIXV>=4110
static unsigned long Soff;		/* shared library VM offset */
int Soff_stat = 0;			/* Soff-available status */
static unsigned long Uoff;		/* user area VM offset */
#endif	/* _AIXV>=4110 */


/*
 * gather_proc_info() - gather process information
 */

void
gather_proc_info()
{
	int i, np;
	struct procinfo *p;
	short pss, sf;
	struct user us;
/*
 * Read the process table.
 */
	if (!P) {
		if ((P = (struct procinfo *)malloc((size_t)PROCSIZE)) == NULL) {
			(void) fprintf(stderr,
				"%s: can't allocate space for 1 procinfo\n",
				Pn);
			exit(1);
		}
		Np = 1;
	}
	while (((np = getproc(P, Np, PROCSIZE)) == -1) && errno == ENOSPC) {
		Np = P->p_pid + 10;
		if ((P = (struct procinfo *)realloc((MALLOC_P *)P,
		    (size_t) (Np * PROCSIZE))) == NULL) {
			(void) fprintf(stderr,
				"%s: no space for %ld procinfo's\n", Pn, Np);
			exit(1);
		}
	}
	iuidcache(Np);
/*
 * Loop through processes.
 */
	for (p = P, Up = &us; np > 0; np--, p++) {
		if (p->p_stat == 0 || p->p_stat == SZOMB)
			continue;
		if (is_proc_excl(p->p_pid, (int)p->p_pgrp, (UID_ARG)p->p_uid,
		    &pss, &sf))
			continue;
	/*
	 * Get user structure.
	 *
	 * If AIX version is below 4.1.1, use getuser().
	 *
	 * If AIX version is 4.1.1: if readx() is disabled, use getuser();
	 * if readx() is enabled, use readx().
	 */

#if	_AIXV>=4110
		if (Fxopt
		&&  kreadx(Uoff, (char *)Up, U_SIZE, p->pi_adspace) == 0)
			i = 1;
		else
			i = 0;
		if (i == 0) {
			if (getuser(p, PROCSIZE, Up, U_SIZE) != 0)
				continue;
		}
#else	/* _AIXV<4110 */
		if (getuser(p, PROCSIZE, Up, U_SIZE) != 0)
			continue;
#endif	/* _AIXV>=4110 */

	/*
	 * Allocate a local process structure and start filling it.
	 */
		if (is_cmd_excl(Up->u_comm, &pss, &sf))
			continue;
		alloc_lproc(p->p_pid, (int)p->p_pgrp, (UID_ARG)p->p_uid,
			Up->u_comm, (int)pss, (int)sf);
		Plf = NULL;
	/*
	 * Save current working directory information.
	 */
		if (Up->u_cdir) {
			alloc_lfile(CWD, -1);
			process_node((caddr_t)Up->u_cdir);
			if (Lf->sf)
				link_lfile();
		}
	/*
	 * Save root directory information.
	 */
		if (Up->u_rdir) {
			alloc_lfile(RTD, -1);
			process_node((caddr_t)Up->u_rdir);
			if (Lf->sf)
				link_lfile();
		}

#if	_AIXV<4100
	/*
	 * Save parent directory information.
	 */
		if (Up->u_pdir) {
			alloc_lfile("  pd", -1);
			process_node((caddr_t)Up->u_pdir);
			if (Lf->sf)
				link_lfile();
		}
#endif	/* _AIXV<4100 */

	/*
	 * Save information on text files.
	 */
		process_text(p->pi_adspace);

	/*
	 * Save information on file descriptors.
	 */
		for (i = 0; i < Up->u_maxofile; i++) {
			if (Up->u_ufd[i].fp) {
				alloc_lfile(NULL, i);
				process_file((KA_T)Up->u_ufd[i].fp);
				if (Lf->sf)
					link_lfile();
			}
		}
	/*
	 * Examine results.
	 */
		if (examine_lproc())
			return;
	}
}


/*
 * get_kernel_access() - get access to kernel memory
 */

static void
get_kernel_access()
{

/*
 * Access /dev/mem.
 */
	if ((Mem = open("/dev/mem", O_RDONLY, 0)) < 0) {
		(void) fprintf(stderr, "%s: can't open /dev/mem: %s\n",
			Pn, strerror(errno));
		exit(1);
	}

#if	defined(WILLDROPGID)
/*
 * If kernel memory isn't coming from KMEM, drop setgid permission
 * before attempting to open the (Memory) file.
 */
	if (Memory)
		(void) dropgid();
#else	/* !defined(WILLDROPGID) */
/*
 * See if the non-KMEM memory file is readable.
 */
	if (Memory && !is_readable(Memory, 1))
		exit(1);
#endif	/* defined(WILLDROPGID) */

/*
 * Access kernel memory file.
 */
	if ((Kmem = open(Memory ? Memory : KMEM, O_RDONLY, 0)) < 0) {
		(void) fprintf(stderr, "%s: can't open %s: %s\n", Pn,
			Memory ? Memory : KMEM, strerror(errno));
		exit(1);
	}

#if	defined(WILLDROPGID)
/*
 * Drop setgid permission, if necessary.
 */
	if (!Memory)
		(void) dropgid();
#endif	/* defined(WILLDROPGID) */

/*
 * Get kernel symbols.
 */
	if (knlist(&Nl, X_NL_NUM, sizeof(struct nlist))
	||  Nl[X_UADDR].n_value == (long)0) {
		(void) fprintf(stderr, "%s: can't get kernel's %s address\n",
			Pn, Nl[X_UADDR].n_name);
		exit(1);
	}

#if	defined(HAS_AFS)
	(void) knlist(&AFSnl, X_AFSNL_NUM, sizeof(struct nlist));
#endif	/* defined(HAS_AFS) */

#if	_AIXV>=4110
/*
 * Get user area and shared library VM offsets for AIX 4.1.1 and above.
 */
	if (Fxopt) {

		struct nlist ll[] = {
			{ "library_anchor"	},
			{ "library_data_handle"	},
			{ NULL			}
		};
		struct ublock *ub;

		if (nlist(N_UNIX, ll) == 0
		&&  ll[0].n_value != (long)0 && ll[1].n_value != (long)0
		&&  kreadx((off_t)(ll[1].n_value & RDXMASK), (char *)&Soff,
			sizeof(Soff), (unsigned long)0)
		== 0)
			Soff_stat++;
		ub = (struct ublock *)Nl[X_UADDR].n_value;
		Uoff = (unsigned long)((int)&ub->ub_user & RDXMASK);
	}
#endif	/* _AIXV>=4110 */

}


/*
 * getle() - get loader entry structure
 */

static struct le *
getle(a, sid, err)
	long a;				/* loader entry kernel address */
	long sid;			/* user structure segment ID */
	char **err;			/* error message (if return is NULL) */
{
	static struct le le;

#if	_AIXV<4110
	if (a < Nl[X_UADDR].n_value) {
		*err = "address too small";
		return(NULL);
	}
	if (((char *)a + sizeof(le)) <= ((char *)Nl[X_UADDR].n_value + U_SIZE))
		return((struct le *)((char *)Up + a - Nl[X_UADDR].n_value));
#endif	/* _AIXV<4110 */

	if ( ! Fxopt) {
		*err = "readx() disabled for Stale Segment ID bug (see -X)";
		return(NULL);
	}

#if	_AIXV>=4110
	if (kreadx((off_t)(a & RDXMASK), (char *)&le, sizeof(le), sid) == 0)
#else	/* _AIXV<4110 */
	if (kreadx((off_t)a, (char *)&le, sizeof(le), sid) == 0)
#endif	/* _AIXV>=4110 */

		return(&le);
	*err = "can't readx()";
	return(NULL);
}


#if	_AIXV>=4110
/*
 * getlenm() - get loader entry file name for AIX 4.1.1 and above
 */

static void
getlenm(le)
	struct le *le;
{
	char buf[LIBNMLN];
	int i;

	if ((le->ft & LIBMASK) != LIBNMCOMP)
		return;
		(off_t)le->nm & RDXMASK;
	if (kreadx((off_t)le->nm & RDXMASK, buf, LIBNMLN, Soff) == 0) {
		buf[LIBNMLN - 1] = '\0';
		i = strlen(buf);
		if (i < (LIBNMLN - 3) && buf[i+1])
			enter_nm(&buf[i+1]);
		else if (buf[0])
			enter_nm(buf);
	}
}
#endif	/* _AIXV>=4110 */



/*
 * initialize() - perform all initialization
 */

void
initialize()
{
	get_kernel_access();
}


/*
 * kread() - read from kernel memory
 */

int
kread(addr, buf, len)
	KA_T addr;			/* kernel memory address */
	char *buf;			/* buffer to receive data */
	READLEN_T len;			/* length to read */
{
	int br;

	if (lseek(Kmem, addr, L_SET) == (off_t)-1L)
		return(-1);
	br = read(Kmem, buf, len);
	return((br == len) ? 0 : 1);
}


/*
 * kreadx() - read kernel segmented memory
 */

int
kreadx(addr, buf, len, sid)
	off_t addr;			/* kernel address */
	char *buf;			/* destination buffer */
	int len;			/* length */
	long sid;			/* segment ID */
{
	int br;

	if (lseek(Mem, addr, L_SET) == (off_t)-1)
		return(1);
	br = readx(Mem, buf, len, sid);
	return (br == len ? 0 : 1);
}


/*
 * process_text() - process text file information
 */

static void
process_text(sid)
	unsigned long sid;		/* user area segment ID */
{
	static struct file **f = NULL;
	char *err, fd[8];
	int i, j, n;
	struct le *le;
	long ll;
	static MALLOC_S nf = 0;
/*
 * Display information on the exec'd entry.
 */
	if ((ll = Up->u_loader[1])) {
		alloc_lfile(" txt", -1);
		if ((le = getle(ll, sid, &err)) != NULL) {
			if (le->fp) {
				process_file((struct file *)le->fp);
				if (Lf->sf) {

#if	_AIXV>=4110
					if (Lf->nm == NULL || Lf->nm[0] == '\0')
						getlenm(le);
#endif	/* _AIXV>=4110 */

					link_lfile();
				}
			}
		} else {
			(void) sprintf(Namech, "text entry at %#x: %s",
				ll, err);
			enter_nm(Namech);
			if (Lf->sf)
				link_lfile();
		}
	}
/*
 * Display the loader list.
 */
	for (i = n = 0, ll = Up->u_loader[0]; ll; ll = (long)le->next) {
		(void) sprintf(fd, " L%02d", i);
		alloc_lfile(fd, -1);
		if ((le = getle(ll, sid, &err)) == NULL) {
			(void) sprintf(Namech, "loader entry at %#x: %s",
				ll, err);
			enter_nm(Namech);
			if (Lf->sf)
				link_lfile();
			return;
		}
	/*
	 * Skip the exec'd file and entries with no file pointers.
	 */
		if (ll == Up->u_loader[1] || ! le->fp)
			continue;
	/*
	 * Skip entries with a file pointer that has already been
	 * displayed.  Record new, unique file pointers.
	 */
		for (j = 0; j < n; j++) {
			if (f[j] == le->fp)
				break;
		}
		if (j < n)
			continue;
		if (f == NULL) {
			if ((f = (struct file **)malloc((MALLOC_S)
				(sizeof(struct file *) * 10)))
			== NULL) {
			    (void) fprintf(stderr,
				"%s: no space for text file pointers\n", Pn);
			    exit(1);
			}
			nf = 10;
		} else if (n >= nf) {
			nf += 10;
			if ((f = (struct file **)realloc((MALLOC_P *)f,
				(MALLOC_S)(nf * sizeof(struct file *))))
			== NULL) {
			    (void) fprintf(stderr,
				"%s: no more space for text file pointers\n",
				Pn);
			    exit(1);
			}
		}
		f[n++] = le->fp;
	/*
	 * Save the loader entry.
	 */
		process_file((struct file *)le->fp);
		if (Lf->sf) {

#if	_AIXV>=4110
			if (Lf->nm == NULL || Lf->nm[0] == '\0')
				getlenm(le);
#endif	/* _AIXV>=4110 */

			link_lfile();
			i++;
		}
	}
}
