/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2013 Gary Mills
 *
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 2012, Joyent, Inc. All rights reserved.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*
 * ps -- print things about processes.
 */
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mkdev.h>
#include <unistd.h>
#include <stdlib.h>
#include <limits.h>
#include <dirent.h>
#include <sys/signal.h>
#include <sys/fault.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <procfs.h>
#include <locale.h>
#include <wctype.h>
#include <wchar.h>
#include <libw.h>
#include <stdarg.h>
#include <sys/proc.h>
#include <sys/pset.h>
#include <project.h>
#include <zone.h>

#define	min(a, b)	((a) > (b) ? (b) : (a))
#define	max(a, b)	((a) < (b) ? (b) : (a))

#define	NTTYS	20	/* initial size of table for -t option  */
#define	SIZ	30	/* initial size of tables for -p, -s, -g, -h and -z */

/*
 * Size of buffer holding args for t, p, s, g, u, U, G, z options.
 * Set to ZONENAME_MAX, the minimum value needed to allow any
 * zone to be specified.
 */
#define	ARGSIZ ZONENAME_MAX

/* Max chars in a user/group name or printed u/g id */
#define	MAXUGNAME (LOGNAME_MAX+2)

/* Structure for storing user or group info */
struct ugdata {
	id_t	id;			/* numeric user-id or group-id */
	char	name[MAXUGNAME+1];	/* user/group name, null terminated */
};

struct ughead {
	size_t	size;		/* number of ugdata structs allocated */
	size_t	nent;		/* number of active entries */
	struct ugdata *ent;	/* pointer to array of actual entries */
};

enum fname {	/* enumeration of field names */
	F_USER,		/* effective user of the process */
	F_RUSER,	/* real user of the process */
	F_GROUP,	/* effective group of the process */
	F_RGROUP,	/* real group of the process */
	F_UID,		/* numeric effective uid of the process */
	F_RUID,		/* numeric real uid of the process */
	F_GID,		/* numeric effective gid of the process */
	F_RGID,		/* numeric real gid of the process */
	F_PID,		/* process id */
	F_PPID,		/* parent process id */
	F_PGID,		/* process group id */
	F_SID,		/* session id */
	F_PSR,		/* bound processor */
	F_LWP,		/* lwp-id */
	F_NLWP,		/* number of lwps */
	F_OPRI,		/* old priority (obsolete) */
	F_PRI,		/* new priority */
	F_F,		/* process flags */
	F_S,		/* letter indicating the state */
	F_C,		/* processor utilization (obsolete) */
	F_PCPU,		/* percent of recently used cpu time */
	F_PMEM,		/* percent of physical memory used (rss) */
	F_OSZ,		/* virtual size of the process in pages */
	F_VSZ,		/* virtual size of the process in kilobytes */
	F_RSS,		/* resident set size of the process in kilobytes */
	F_NICE,		/* "nice" value of the process */
	F_CLASS,	/* scheduler class */
	F_STIME,	/* start time of the process, hh:mm:ss or Month Day */
	F_ETIME,	/* elapsed time of the process, [[dd-]hh:]mm:ss */
	F_TIME,		/* cpu time of the process, [[dd-]hh:]mm:ss */
	F_TTY,		/* name of the controlling terminal */
	F_ADDR,		/* address of the process (obsolete) */
	F_WCHAN,	/* wait channel (sleep condition variable) */
	F_FNAME,	/* file name of command */
	F_COMM,		/* name of command (argv[0] value) */
	F_ARGS,		/* name of command plus all its arguments */
	F_TASKID,	/* task id */
	F_PROJID,	/* project id */
	F_PROJECT,	/* project name of the process */
	F_PSET,		/* bound processor set */
	F_ZONE,		/* zone name */
	F_ZONEID,	/* zone id */
	F_CTID,		/* process contract id */
	F_LGRP,		/* process home lgroup */
	F_DMODEL	/* process data model */
};

struct field {
	struct field	*next;		/* linked list */
	int		fname;		/* field index */
	const char	*header;	/* header to use */
	int		width;		/* width of field */
};

static	struct field *fields = NULL;	/* fields selected via -o */
static	struct field *last_field = NULL;
static	int do_header = 0;
static	struct timeval now;

/* array of defined fields, in fname order */
struct def_field {
	const char *fname;
	const char *header;
	int width;
	int minwidth;
};

static struct def_field fname[] = {
	/* fname	header		width	minwidth */
	{ "user",	"USER",		8,	8	},
	{ "ruser",	"RUSER",	8,	8	},
	{ "group",	"GROUP",	8,	8	},
	{ "rgroup",	"RGROUP",	8,	8	},
	{ "uid",	"UID",		5,	5	},
	{ "ruid",	"RUID",		5,	5	},
	{ "gid",	"GID",		5,	5	},
	{ "rgid",	"RGID",		5,	5	},
	{ "pid",	"PID",		5,	5	},
	{ "ppid",	"PPID",		5,	5	},
	{ "pgid",	"PGID",		5,	5	},
	{ "sid",	"SID",		5,	5	},
	{ "psr",	"PSR",		3,	2	},
	{ "lwp",	"LWP",		6,	2	},
	{ "nlwp",	"NLWP",		4,	2	},
	{ "opri",	"PRI",		3,	2	},
	{ "pri",	"PRI",		3,	2	},
	{ "f",		"F",		2,	2	},
	{ "s",		"S",		1,	1	},
	{ "c",		"C",		2,	2	},
	{ "pcpu",	"%CPU",		4,	4	},
	{ "pmem",	"%MEM",		4,	4	},
	{ "osz",	"SZ",		4,	4	},
	{ "vsz",	"VSZ",		4,	4	},
	{ "rss",	"RSS",		4,	4	},
	{ "nice",	"NI",		2,	2	},
	{ "class",	"CLS",		4,	2	},
	{ "stime",	"STIME",	8,	8	},
	{ "etime",	"ELAPSED",	11,	7	},
	{ "time",	"TIME",		11,	5	},
	{ "tty",	"TT",		7,	7	},
#ifdef _LP64
	{ "addr",	"ADDR",		16,	8	},
	{ "wchan",	"WCHAN",	16,	8	},
#else
	{ "addr",	"ADDR",		8,	8	},
	{ "wchan",	"WCHAN",	8,	8	},
#endif
	{ "fname",	"COMMAND",	8,	8	},
	{ "comm",	"COMMAND",	80,	8	},
	{ "args",	"COMMAND",	80,	80	},
	{ "taskid",	"TASKID",	5,	5	},
	{ "projid",	"PROJID",	5,	5	},
	{ "project",	"PROJECT",	8,	8	},
	{ "pset",	"PSET",		3,	3	},
	{ "zone",	"ZONE",		8,	8	},
	{ "zoneid",	"ZONEID",	5,	5	},
	{ "ctid",	"CTID",		5,	5	},
	{ "lgrp",	"LGRP",		4,	2 	},
	{ "dmodel",	"DMODEL",	6,	6 	},
};

#define	NFIELDS	(sizeof (fname) / sizeof (fname[0]))

static	int	retcode = 1;
static	int	lflg;
static	int	Aflg;
static	int	uflg;
static	int	Uflg;
static	int	Gflg;
static	int	aflg;
static	int	dflg;
static	int	Lflg;
static	int	Pflg;
static	int	Wflg;
static	int	yflg;
static	int	pflg;
static	int	fflg;
static	int	cflg;
static	int	jflg;
static	int	gflg;
static	int	sflg;
static	int	tflg;
static	int	zflg;
static	int	Zflg;
static	int	hflg;
static	int	Hflg;
static	uid_t	tuid = (uid_t)-1;
static	int	errflg;

static	int	ndev;		/* number of devices */
static	int	maxdev;		/* number of devl structures allocated */

#define	DNINCR	100
#define	DNSIZE	14
static struct devl {		/* device list   */
	char	dname[DNSIZE];	/* device name   */
	dev_t	ddev;		/* device number */
} *devl;

static	struct tty {
	char *tname;
	dev_t tdev;
} *tty = NULL;			/* for t option */
static	size_t	ttysz = 0;
static	int	ntty = 0;

static	pid_t	*pid = NULL;	/* for p option */
static	size_t	pidsz = 0;
static	size_t	npid = 0;

static	int	*lgrps = NULL;	/* list of lgroup IDs for for h option */
static	size_t	lgrps_size = 0;	/* size of the lgrps list */
static	size_t	nlgrps = 0;	/* number elements in the list */

/* Maximum possible lgroup ID value */
#define	MAX_LGRP_ID 256

static	pid_t	*grpid = NULL;	/* for g option */
static	size_t	grpidsz = 0;
static	int	ngrpid = 0;

static	pid_t	*sessid = NULL;	/* for s option */
static	size_t	sessidsz = 0;
static	int	nsessid = 0;

static	zoneid_t *zoneid = NULL; /* for z option */
static	size_t	zoneidsz = 0;
static	int	nzoneid = 0;

static	int	kbytes_per_page;
static	int	pidwidth;

static	char	*procdir = "/proc";	/* standard /proc directory */

static struct ughead	euid_tbl;	/* table to store selected euid's */
static struct ughead	ruid_tbl;	/* table to store selected real uid's */
static struct ughead	egid_tbl;	/* table to store selected egid's */
static struct ughead	rgid_tbl;	/* table to store selected real gid's */
static prheader_t *lpsinfobuf;		/* buffer to contain lpsinfo */
static size_t	lpbufsize;

/*
 * This constant defines the sentinal number of process IDs below which we
 * only examine individual entries in /proc rather than scanning through
 * /proc. This optimization is a huge win in the common case.
 */
#define	PTHRESHOLD	40

#define	UCB_OPTS	"-aceglnrtuvwxSU"

static	void	usage(void);
static	char	*getarg(char **);
static	char	*parse_format(char *);
static	char	*gettty(psinfo_t *);
static	int	prfind(int, psinfo_t *, char **);
static	void	prcom(psinfo_t *, char *);
static	void	prtpct(ushort_t, int);
static	void	print_time(time_t, int);
static	void	print_field(psinfo_t *, struct field *, const char *);
static	void	print_zombie_field(psinfo_t *, struct field *, const char *);
static	void	pr_fields(psinfo_t *, const char *,
		void (*print_fld)(psinfo_t *, struct field *, const char *));
static	int	search(pid_t *, int, pid_t);
static	void	add_ugentry(struct ughead *, char *);
static	int	uconv(struct ughead *);
static	int	gconv(struct ughead *);
static	int	ugfind(id_t, struct ughead *);
static	void	prtime(timestruc_t, int, int);
static	void	przom(psinfo_t *);
static	int	namencnt(char *, int, int);
static	char	*err_string(int);
static	int	print_proc(char *pname);
static	time_t	delta_secs(const timestruc_t *);
static	int	str2id(const char *, pid_t *, long, long);
static	int	str2uid(const char *,  uid_t *, unsigned long, unsigned long);
static	void	*Realloc(void *, size_t);
static	int	pidcmp(const void *p1, const void *p2);

extern	int	ucbmain(int, char **);
static	int	stdmain(int, char **);

int
main(int argc, char **argv)
{
	const char *me;

	/*
	 * The original two ps'es are linked in a single binary;
	 * their main()s are renamed to stdmain for /usr/bin/ps and
	 * ucbmain for /usr/ucb/ps.
	 * We try to figure out which instance of ps the user wants to run.
	 * Traditionally, the UCB variant doesn't require the flag argument
	 * start with a "-".  If the first argument doesn't start with a
	 * "-", we call "ucbmain".
	 * If there's a first argument and it starts with a "-", we check
	 * whether any of the options isn't acceptable to "ucbmain"; in that
	 * case we run "stdmain".
	 * If we can't tell from the options which main to call, we check
	 * the binary we are running.  We default to "stdmain" but
	 * any mention in the executable name of "ucb" causes us to call
	 * ucbmain.
	 */
	if (argv[1] != NULL) {
		if (argv[1][0] != '-')
			return (ucbmain(argc, argv));
		else if (argv[1][strspn(argv[1], UCB_OPTS)] != '\0')
			return (stdmain(argc, argv));
	}

	me = getexecname();

	if (me != NULL && strstr(me, "ucb") != NULL)
		return (ucbmain(argc, argv));
	else
		return (stdmain(argc, argv));
}

static int
stdmain(int argc, char **argv)
{
	char	*p;
	char	*p1;
	char	*parg;
	int	c;
	int	i;
	int	pgerrflg = 0;	/* err flg: non-numeric arg w/p & g options */
	size_t	size, len;
	DIR	*dirp;
	struct dirent *dentp;
	pid_t	maxpid;
	pid_t	id;
	int	ret;
	char	loc_stime_str[32];

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
#define	TEXT_DOMAIN	"SYS_TEST"	/* Use this only if it weren't */
#endif
	(void) textdomain(TEXT_DOMAIN);

	(void) memset(&euid_tbl, 0, sizeof (euid_tbl));
	(void) memset(&ruid_tbl, 0, sizeof (ruid_tbl));
	(void) memset(&egid_tbl, 0, sizeof (egid_tbl));
	(void) memset(&rgid_tbl, 0, sizeof (rgid_tbl));

	kbytes_per_page = sysconf(_SC_PAGESIZE) / 1024;

	(void) gettimeofday(&now, NULL);

	/*
	 * calculate width of pid fields based on configured MAXPID
	 * (must be at least 5 to retain output format compatibility)
	 */
	id = maxpid = (pid_t)sysconf(_SC_MAXPID);
	pidwidth = 1;
	while ((id /= 10) > 0)
		++pidwidth;
	pidwidth = pidwidth < 5 ? 5 : pidwidth;

	fname[F_PID].width = fname[F_PPID].width = pidwidth;
	fname[F_PGID].width = fname[F_SID].width = pidwidth;

	/*
	 * TRANSLATION_NOTE
	 * Specify the printf format with width and precision for
	 * the STIME field.
	 */
	len = snprintf(loc_stime_str, sizeof (loc_stime_str),
	    dcgettext(NULL, "%8.8s", LC_TIME), "STIME");
	if (len >= sizeof (loc_stime_str))
		len = sizeof (loc_stime_str) - 1;

	fname[F_STIME].width = fname[F_STIME].minwidth = len;

	while ((c = getopt(argc, argv, "jlfceAadLPWyZHh:t:p:g:u:U:G:n:s:o:z:"))
	    != EOF)
		switch (c) {
		case 'H':		/* Show home lgroups */
			Hflg++;
			break;
		case 'h':
			/*
			 * Show processes/threads with given home lgroups
			 */
			hflg++;
			p1 = optarg;
			do {
				int id;

				/*
				 * Get all IDs in the list, verify for
				 * correctness and place in lgrps array.
				 */
				parg = getarg(&p1);
				/* Convert string to integer */
				ret = str2id(parg, (pid_t *)&id, 0,
				    MAX_LGRP_ID);
				/* Complain if ID didn't parse correctly */
				if (ret != 0) {
					pgerrflg++;
					(void) fprintf(stderr,
					    gettext("ps: %s "), parg);
					if (ret == EINVAL)
						(void) fprintf(stderr,
						    gettext("is an invalid "
						    "non-numeric argument"));
					else
						(void) fprintf(stderr,
						    gettext("exceeds valid "
						    "range"));
					(void) fprintf(stderr,
					    gettext(" for -h option\n"));
					continue;
				}

				/* Extend lgrps array if needed */
				if (nlgrps == lgrps_size) {
					/* Double the size of the lgrps array */
					if (lgrps_size == 0)
						lgrps_size = SIZ;
					lgrps_size *= 2;
					lgrps = Realloc(lgrps,
					    lgrps_size * sizeof (int));
				}
				/* place the id in the lgrps table */
				lgrps[nlgrps++] = id;
			} while (*p1);
			break;
		case 'l':		/* long listing */
			lflg++;
			break;
		case 'f':		/* full listing */
			fflg++;
			break;
		case 'j':
			jflg++;
			break;
		case 'c':
			/*
			 * Format output to reflect scheduler changes:
			 * high numbers for high priorities and don't
			 * print nice or p_cpu values.  'c' option only
			 * effective when used with 'l' or 'f' options.
			 */
			cflg++;
			break;
		case 'A':		/* list every process */
		case 'e':		/* (obsolete) list every process */
			Aflg++;
			tflg = Gflg = Uflg = uflg = pflg = gflg = sflg = 0;
			zflg = hflg = 0;
			break;
		case 'a':
			/*
			 * Same as 'e' except no session group leaders
			 * and no non-terminal processes.
			 */
			aflg++;
			break;
		case 'd':	/* same as e except no session leaders */
			dflg++;
			break;
		case 'L':	/* show lwps */
			Lflg++;
			break;
		case 'P':	/* show bound processor */
			Pflg++;
			break;
		case 'W':	/* truncate long names */
			Wflg++;
			break;
		case 'y':	/* omit F & ADDR, report RSS & SZ in Kby */
			yflg++;
			break;
		case 'n':	/* no longer needed; retain as no-op */
			(void) fprintf(stderr,
			    gettext("ps: warning: -n option ignored\n"));
			break;
		case 't':		/* terminals */
#define	TSZ	30
			tflg++;
			p1 = optarg;
			do {
				char nambuf[TSZ+6];	/* for "/dev/" + '\0' */
				struct stat64 s;
				parg = getarg(&p1);
				p = Realloc(NULL, TSZ+1);	/* for '\0' */
				/* zero the buffer before using it */
				p[0] = '\0';
				size = TSZ;
				if (isdigit(*parg)) {
					(void) strcpy(p, "tty");
					size -= 3;
				}
				(void) strncat(p, parg, size);
				if (ntty == ttysz) {
					if ((ttysz *= 2) == 0)
						ttysz = NTTYS;
					tty = Realloc(tty,
					    (ttysz + 1) * sizeof (struct tty));
				}
				tty[ntty].tdev = PRNODEV;
				(void) strcpy(nambuf, "/dev/");
				(void) strcat(nambuf, p);
				if (stat64(nambuf, &s) == 0)
					tty[ntty].tdev = s.st_rdev;
				tty[ntty++].tname = p;
			} while (*p1);
			break;
		case 'p':		/* proc ids */
			pflg++;
			p1 = optarg;
			do {
				pid_t id;

				parg = getarg(&p1);
				if ((ret = str2id(parg, &id, 0, maxpid)) != 0) {
					pgerrflg++;
					(void) fprintf(stderr,
					    gettext("ps: %s "), parg);
					if (ret == EINVAL)
						(void) fprintf(stderr,
						    gettext("is an invalid "
						    "non-numeric argument"));
					else
						(void) fprintf(stderr,
						    gettext("exceeds valid "
						    "range"));
					(void) fprintf(stderr,
					    gettext(" for -p option\n"));
					continue;
				}

				if (npid == pidsz) {
					if ((pidsz *= 2) == 0)
						pidsz = SIZ;
					pid = Realloc(pid,
					    pidsz * sizeof (pid_t));
				}
				pid[npid++] = id;
			} while (*p1);
			break;
		case 's':		/* session */
			sflg++;
			p1 = optarg;
			do {
				pid_t id;

				parg = getarg(&p1);
				if ((ret = str2id(parg, &id, 0, maxpid)) != 0) {
					pgerrflg++;
					(void) fprintf(stderr,
					    gettext("ps: %s "), parg);
					if (ret == EINVAL)
						(void) fprintf(stderr,
						    gettext("is an invalid "
						    "non-numeric argument"));
					else
						(void) fprintf(stderr,
						    gettext("exceeds valid "
						    "range"));
					(void) fprintf(stderr,
					    gettext(" for -s option\n"));
					continue;
				}

				if (nsessid == sessidsz) {
					if ((sessidsz *= 2) == 0)
						sessidsz = SIZ;
					sessid = Realloc(sessid,
					    sessidsz * sizeof (pid_t));
				}
				sessid[nsessid++] = id;
			} while (*p1);
			break;
		case 'g':		/* proc group */
			gflg++;
			p1 = optarg;
			do {
				pid_t id;

				parg = getarg(&p1);
				if ((ret = str2id(parg, &id, 0, maxpid)) != 0) {
					pgerrflg++;
					(void) fprintf(stderr,
					    gettext("ps: %s "), parg);
					if (ret == EINVAL)
						(void) fprintf(stderr,
						    gettext("is an invalid "
						    "non-numeric argument"));
					else
						(void) fprintf(stderr,
						    gettext("exceeds valid "
						    "range"));
					(void) fprintf(stderr,
					    gettext(" for -g option\n"));
					continue;
				}

				if (ngrpid == grpidsz) {
					if ((grpidsz *= 2) == 0)
						grpidsz = SIZ;
					grpid = Realloc(grpid,
					    grpidsz * sizeof (pid_t));
				}
				grpid[ngrpid++] = id;
			} while (*p1);
			break;
		case 'u':		/* effective user name or number */
			uflg++;
			p1 = optarg;
			do {
				parg = getarg(&p1);
				add_ugentry(&euid_tbl, parg);
			} while (*p1);
			break;
		case 'U':		/* real user name or number */
			Uflg++;
			p1 = optarg;
			do {
				parg = getarg(&p1);
				add_ugentry(&ruid_tbl, parg);
			} while (*p1);
			break;
		case 'G':		/* real group name or number */
			Gflg++;
			p1 = optarg;
			do {
				parg = getarg(&p1);
				add_ugentry(&rgid_tbl, parg);
			} while (*p1);
			break;
		case 'o':		/* output format */
			p = optarg;
			while ((p = parse_format(p)) != NULL)
				;
			break;
		case 'z':		/* zone name or number */
			zflg++;
			p1 = optarg;
			do {
				zoneid_t id;

				parg = getarg(&p1);
				if (zone_get_id(parg, &id) != 0) {
					pgerrflg++;
					(void) fprintf(stderr,
					    gettext("ps: unknown zone %s\n"),
					    parg);
					continue;
				}

				if (nzoneid == zoneidsz) {
					if ((zoneidsz *= 2) == 0)
						zoneidsz = SIZ;
					zoneid = Realloc(zoneid,
					    zoneidsz * sizeof (zoneid_t));
				}
				zoneid[nzoneid++] = id;
			} while (*p1);
			break;
		case 'Z':		/* show zone name */
			Zflg++;
			break;
		default:			/* error on ? */
			errflg++;
			break;
		}

	if (errflg || optind < argc || pgerrflg)
		usage();

	if (tflg)
		tty[ntty].tname = NULL;
	/*
	 * If an appropriate option has not been specified, use the
	 * current terminal and effective uid as the default.
	 */
	if (!(aflg|Aflg|dflg|Gflg|hflg|Uflg|uflg|tflg|pflg|gflg|sflg|zflg)) {
		psinfo_t info;
		int procfd;
		char *name;
		char pname[100];

		/* get our own controlling tty name using /proc */
		(void) snprintf(pname, sizeof (pname),
		    "%s/self/psinfo", procdir);
		if ((procfd = open(pname, O_RDONLY)) < 0 ||
		    read(procfd, (char *)&info, sizeof (info)) < 0 ||
		    info.pr_ttydev == PRNODEV) {
			(void) fprintf(stderr,
			    gettext("ps: no controlling terminal\n"));
			exit(1);
		}
		(void) close(procfd);

		i = 0;
		name = gettty(&info);
		if (*name == '?') {
			(void) fprintf(stderr,
			    gettext("ps: can't find controlling terminal\n"));
			exit(1);
		}
		if (ntty == ttysz) {
			if ((ttysz *= 2) == 0)
				ttysz = NTTYS;
			tty = Realloc(tty, (ttysz + 1) * sizeof (struct tty));
		}
		tty[ntty].tdev = info.pr_ttydev;
		tty[ntty++].tname = name;
		tty[ntty].tname = NULL;
		tflg++;
		tuid = getuid();
	}
	if (Aflg) {
		Gflg = Uflg = uflg = pflg = sflg = gflg = aflg = dflg = 0;
		zflg = hflg = 0;
	}
	if (Aflg | aflg | dflg)
		tflg = 0;

	i = 0;		/* prepare to exit on name lookup errors */
	i += uconv(&euid_tbl);
	i += uconv(&ruid_tbl);
	i += gconv(&egid_tbl);
	i += gconv(&rgid_tbl);
	if (i)
		exit(1);

	/* allocate a buffer for lwpsinfo structures */
	lpbufsize = 4096;
	if (Lflg && (lpsinfobuf = malloc(lpbufsize)) == NULL) {
		(void) fprintf(stderr,
		    gettext("ps: no memory\n"));
		exit(1);
	}

	if (fields) {	/* print user-specified header */
		if (do_header) {
			struct field *f;

			for (f = fields; f != NULL; f = f->next) {
				if (f != fields)
					(void) printf(" ");
				switch (f->fname) {
				case F_TTY:
					(void) printf("%-*s",
					    f->width, f->header);
					break;
				case F_FNAME:
				case F_COMM:
				case F_ARGS:
					/*
					 * Print these headers full width
					 * unless they appear at the end.
					 */
					if (f->next != NULL) {
						(void) printf("%-*s",
						    f->width, f->header);
					} else {
						(void) printf("%s",
						    f->header);
					}
					break;
				default:
					(void) printf("%*s",
					    f->width, f->header);
					break;
				}
			}
			(void) printf("\n");
		}
	} else {	/* print standard header */
		/*
		 * All fields before 'PID' are printed with a trailing space
		 * as a separator and that is how we print the headers too.
		 */
		if (lflg) {
			if (yflg)
				(void) printf("S ");
			else
				(void) printf(" F S ");
		}
		if (Zflg)
			(void) printf("    ZONE ");
		if (fflg) {
			(void) printf("     UID ");
		} else if (lflg)
			(void) printf("   UID ");

		(void) printf("%*s", pidwidth,  "PID");
		if (lflg || fflg)
			(void) printf(" %*s", pidwidth, "PPID");
		if (jflg)
			(void) printf(" %*s %*s", pidwidth, "PGID",
			    pidwidth, "SID");
		if (Lflg)
			(void) printf("   LWP");
		if (Pflg)
			(void) printf(" PSR");
		if (Lflg && fflg)
			(void) printf("  NLWP");
		if (cflg)
			(void) printf("  CLS PRI");
		else if (lflg || fflg) {
			(void) printf("   C");
			if (lflg)
				(void) printf(" PRI NI");
		}
		if (lflg) {
			if (yflg)
				(void) printf("   RSS     SZ    WCHAN");
			else
				(void) printf("     ADDR     SZ    WCHAN");
		}
		if (fflg)
			(void) printf(" %s", loc_stime_str);
		if (Hflg)
			(void) printf(" LGRP");
		if (Lflg)
			(void) printf(" TTY        LTIME CMD\n");
		else
			(void) printf(" TTY         TIME CMD\n");
	}


	if (pflg && !(aflg|Aflg|dflg|Gflg|Uflg|uflg|hflg|tflg|gflg|sflg|zflg) &&
	    npid <= PTHRESHOLD) {
		/*
		 * If we are looking at specific processes go straight
		 * to their /proc entries and don't scan /proc.
		 */
		int i;

		(void) qsort(pid, npid, sizeof (pid_t), pidcmp);
		for (i = 0; i < npid; i++) {
			char pname[12];

			if (i >= 1 && pid[i] == pid[i - 1])
				continue;
			(void) sprintf(pname, "%d", (int)pid[i]);
			if (print_proc(pname) == 0)
				retcode = 0;
		}
	} else {
		/*
		 * Determine which processes to print info about by searching
		 * the /proc directory and looking at each process.
		 */
		if ((dirp = opendir(procdir)) == NULL) {
			(void) fprintf(stderr,
			    gettext("ps: cannot open PROC directory %s\n"),
			    procdir);
			exit(1);
		}

		/* for each active process --- */
		while ((dentp = readdir(dirp)) != NULL) {
			if (dentp->d_name[0] == '.')    /* skip . and .. */
				continue;
			if (print_proc(dentp->d_name) == 0)
				retcode = 0;
		}

		(void) closedir(dirp);
	}
	return (retcode);
}


int
print_proc(char *pid_name)
{
	char	pname[PATH_MAX];
	int	pdlen;
	int	found;
	int	procfd; /* filedescriptor for /proc/nnnnn/psinfo */
	char	*tp;    /* ptr to ttyname,  if any */
	psinfo_t info;  /* process information from /proc */
	lwpsinfo_t *lwpsinfo;   /* array of lwpsinfo structs */

	pdlen = snprintf(pname, sizeof (pname), "%s/%s/", procdir, pid_name);
	if (pdlen >= sizeof (pname) - 10)
		return (1);
retry:
	(void) strcpy(&pname[pdlen], "psinfo");
	if ((procfd = open(pname, O_RDONLY)) == -1) {
		/* Process may have exited meanwhile. */
		return (1);
	}
	/*
	 * Get the info structure for the process and close quickly.
	 */
	if (read(procfd, (char *)&info, sizeof (info)) < 0) {
		int	saverr = errno;

		(void) close(procfd);
		if (saverr == EAGAIN)
			goto retry;
		if (saverr != ENOENT)
			(void) fprintf(stderr,
			    gettext("ps: read() on %s: %s\n"),
			    pname, err_string(saverr));
		return (1);
	}
	(void) close(procfd);

	found = 0;
	if (info.pr_lwp.pr_state == 0)	/* can't happen? */
		return (1);

	/*
	 * Omit session group leaders for 'a' and 'd' options.
	 */
	if ((info.pr_pid == info.pr_sid) && (dflg || aflg))
		return (1);
	if (Aflg || dflg)
		found++;
	else if (pflg && search(pid, npid, info.pr_pid))
		found++;	/* ppid in p option arg list */
	else if (uflg && ugfind((id_t)info.pr_euid, &euid_tbl))
		found++;	/* puid in u option arg list */
	else if (Uflg && ugfind((id_t)info.pr_uid, &ruid_tbl))
		found++;	/* puid in U option arg list */
#ifdef NOT_YET
	else if (gflg && ugfind((id_t)info.pr_egid, &egid_tbl))
		found++;	/* pgid in g option arg list */
#endif	/* NOT_YET */
	else if (Gflg && ugfind((id_t)info.pr_gid, &rgid_tbl))
		found++;	/* pgid in G option arg list */
	else if (gflg && search(grpid, ngrpid, info.pr_pgid))
		found++;	/* grpid in g option arg list */
	else if (sflg && search(sessid, nsessid, info.pr_sid))
		found++;	/* sessid in s option arg list */
	else if (zflg && search(zoneid, nzoneid, info.pr_zoneid))
		found++;	/* zoneid in z option arg list */
	else if (hflg && search((pid_t *)lgrps, nlgrps, info.pr_lwp.pr_lgrp))
		found++;	/* home lgroup in h option arg list */
	if (!found && !tflg && !aflg)
		return (1);
	if (!prfind(found, &info, &tp))
		return (1);
	if (Lflg && (info.pr_nlwp + info.pr_nzomb) > 1) {
		ssize_t prsz;

		(void) strcpy(&pname[pdlen], "lpsinfo");
		if ((procfd = open(pname, O_RDONLY)) == -1)
			return (1);
		/*
		 * Get the info structures for the lwps.
		 */
		prsz = read(procfd, lpsinfobuf, lpbufsize);
		if (prsz == -1) {
			int	saverr = errno;

			(void) close(procfd);
			if (saverr == EAGAIN)
				goto retry;
			if (saverr != ENOENT)
				(void) fprintf(stderr,
				    gettext("ps: read() on %s: %s\n"),
				    pname, err_string(saverr));
			return (1);
		}
		(void) close(procfd);
		if (prsz == lpbufsize) {
			/*
			 * buffer overflow. Realloc new buffer.
			 * Error handling is done in Realloc().
			 */
			lpbufsize *= 2;
			lpsinfobuf = Realloc(lpsinfobuf, lpbufsize);
			goto retry;
		}
		if (lpsinfobuf->pr_nent != (info.pr_nlwp + info.pr_nzomb))
			goto retry;
		lwpsinfo = (lwpsinfo_t *)(lpsinfobuf + 1);
	}
	if (!Lflg || (info.pr_nlwp + info.pr_nzomb) <= 1) {
		prcom(&info, tp);
	} else {
		int nlwp = 0;

		do {
			info.pr_lwp = *lwpsinfo;
			prcom(&info, tp);
			/* LINTED improper alignment */
			lwpsinfo = (lwpsinfo_t *)((char *)lwpsinfo +
			    lpsinfobuf->pr_entsize);
		} while (++nlwp < lpsinfobuf->pr_nent);
	}
	return (0);
}

static int
field_cmp(const void *l, const void *r)
{
	struct def_field *lhs = *((struct def_field **)l);
	struct def_field *rhs = *((struct def_field **)r);

	return (strcmp(lhs->fname, rhs->fname));
}

static void
usage(void)		/* print usage message and quit */
{
	struct def_field *df, *sorted[NFIELDS];
	int pos = 80, i = 0;

	static char usage1[] =
	    "ps [ -aAdefHlcjLPWyZ ] [ -o format ] [ -t termlist ]";
	static char usage2[] =
	    "\t[ -u userlist ] [ -U userlist ] [ -G grouplist ]";
	static char usage3[] =
	    "\t[ -p proclist ] [ -g pgrplist ] [ -s sidlist ]";
	static char usage4[] =
	    "\t[ -z zonelist ] [-h lgrplist]";
	static char usage5[] =
	    "  'format' is one or more of:";

	(void) fprintf(stderr,
	    gettext("usage: %s\n%s\n%s\n%s\n%s"),
	    gettext(usage1), gettext(usage2), gettext(usage3),
	    gettext(usage4), gettext(usage5));

	/*
	 * Now print out the possible output formats such that they neatly fit
	 * into eighty columns.  Note that the fact that we are determining
	 * this output programmatically means that a gettext() is impossible --
	 * but it would be a mistake to localize the output formats anyway as
	 * they are tokens for input, not output themselves.
	 */
	for (df = &fname[0]; df < &fname[NFIELDS]; df++)
		sorted[i++] = df;

	(void) qsort(sorted, NFIELDS, sizeof (void *), field_cmp);

	for (i = 0; i < NFIELDS; i++) {
		if (pos + strlen((df = sorted[i])->fname) + 1 >= 80) {
			(void) fprintf(stderr, "\n\t");
			pos = 8;
		}

		(void) fprintf(stderr, "%s%s", pos > 8 ? " " : "", df->fname);
		pos += strlen(df->fname) + 1;
	}

	(void) fprintf(stderr, "\n");

	exit(1);
}

/*
 * getarg() finds the next argument in list and copies arg into argbuf.
 * p1 first pts to arg passed back from getopt routine.  p1 is then
 * bumped to next character that is not a comma or blank -- p1 NULL
 * indicates end of list.
 */
static char *
getarg(char **pp1)
{
	static char argbuf[ARGSIZ];
	char *p1 = *pp1;
	char *parga = argbuf;
	int c;

	while ((c = *p1) != '\0' && (c == ',' || isspace(c)))
		p1++;

	while ((c = *p1) != '\0' && c != ',' && !isspace(c)) {
		if (parga < argbuf + ARGSIZ - 1)
			*parga++ = c;
		p1++;
	}
	*parga = '\0';

	while ((c = *p1) != '\0' && (c == ',' || isspace(c)))
		p1++;

	*pp1 = p1;

	return (argbuf);
}

/*
 * parse_format() takes the argument to the -o option,
 * sets up the next output field structure, and returns
 * a pointer to any further output field specifier(s).
 * As a side-effect, it increments errflg if encounters a format error.
 */
static char *
parse_format(char *arg)
{
	int c;
	char *name;
	char *header = NULL;
	int width = 0;
	struct def_field *df;
	struct field *f;

	while ((c = *arg) != '\0' && (c == ',' || isspace(c)))
		arg++;
	if (c == '\0')
		return (NULL);
	name = arg;
	arg = strpbrk(arg, " \t\r\v\f\n,=");
	if (arg != NULL) {
		c = *arg;
		*arg++ = '\0';
		if (c == '=') {
			char *s;

			header = arg;
			arg = NULL;
			width = strlen(header);
			s = header + width;
			while (s > header && isspace(*--s))
				*s = '\0';
			while (isspace(*header))
				header++;
		}
	}
	for (df = &fname[0]; df < &fname[NFIELDS]; df++)
		if (strcmp(name, df->fname) == 0) {
			if (strcmp(name, "lwp") == 0)
				Lflg++;
			break;
		}
	if (df >= &fname[NFIELDS]) {
		(void) fprintf(stderr,
		    gettext("ps: unknown output format: -o %s\n"),
		    name);
		errflg++;
		return (arg);
	}
	if ((f = malloc(sizeof (*f))) == NULL) {
		(void) fprintf(stderr,
		    gettext("ps: malloc() for output format failed, %s\n"),
		    err_string(errno));
		exit(1);
	}
	f->next = NULL;
	f->fname = df - &fname[0];
	f->header = header? header : df->header;
	if (width == 0)
		width = df->width;
	if (*f->header != '\0')
		do_header = 1;
	f->width = max(width, df->minwidth);

	if (fields == NULL)
		fields = last_field = f;
	else {
		last_field->next = f;
		last_field = f;
	}

	return (arg);
}

static char *
devlookup(dev_t ddev)
{
	struct devl *dp;
	int i;

	for (dp = devl, i = 0; i < ndev; dp++, i++) {
		if (dp->ddev == ddev)
			return (dp->dname);
	}
	return (NULL);
}

static char *
devadd(char *name, dev_t ddev)
{
	struct devl *dp;
	int leng, start, i;

	if (ndev == maxdev) {
		maxdev += DNINCR;
		devl = Realloc(devl, maxdev * sizeof (struct devl));
	}
	dp = &devl[ndev++];

	dp->ddev = ddev;
	if (name == NULL) {
		(void) strcpy(dp->dname, "??");
		return (dp->dname);
	}

	leng = strlen(name);
	/* Strip off /dev/ */
	if (leng < DNSIZE + 4)
		(void) strcpy(dp->dname, &name[5]);
	else {
		start = leng - DNSIZE - 1;

		for (i = start; i < leng && name[i] != '/'; i++)
				;
		if (i == leng)
			(void) strncpy(dp->dname, &name[start], DNSIZE);
		else
			(void) strncpy(dp->dname, &name[i+1], DNSIZE);
	}
	return (dp->dname);
}

/*
 * gettty returns the user's tty number or ? if none.
 */
static char *
gettty(psinfo_t *psinfo)
{
	extern char *_ttyname_dev(dev_t, char *, size_t);
	static zoneid_t zid = -1;
	char devname[TTYNAME_MAX];
	char *retval;

	if (zid == -1)
		zid = getzoneid();

	if (psinfo->pr_ttydev == PRNODEV || psinfo->pr_zoneid != zid)
		return ("?");

	if ((retval = devlookup(psinfo->pr_ttydev)) != NULL)
		return (retval);

	retval = _ttyname_dev(psinfo->pr_ttydev, devname, sizeof (devname));

	return (devadd(retval, psinfo->pr_ttydev));
}

/*
 * Find the process's tty and return 1 if process is to be printed.
 */
static int
prfind(int found, psinfo_t *psinfo, char **tpp)
{
	char	*tp;
	struct tty *ttyp;

	if (psinfo->pr_nlwp == 0) {
		/* process is a zombie */
		*tpp = "?";
		if (tflg && !found)
			return (0);
		return (1);
	}

	/*
	 * Get current terminal.  If none ("?") and 'a' is set, don't print
	 * info.  If 't' is set, check if term is in list of desired terminals
	 * and print it if it is.
	 */
	tp = gettty(psinfo);
	if (aflg && *tp == '?') {
		*tpp = tp;
		return (0);
	}
	if (tflg && !found) {
		int match = 0;
		char *other = NULL;
		for (ttyp = tty; ttyp->tname != NULL; ttyp++) {
			/*
			 * Look for a name match
			 */
			if (strcmp(tp, ttyp->tname) == 0) {
				match = 1;
				break;
			}
			/*
			 * Look for same device under different names.
			 */
			if ((other == NULL) &&
			    (ttyp->tdev != PRNODEV) &&
			    (psinfo->pr_ttydev == ttyp->tdev))
				other = ttyp->tname;
		}
		if (!match && (other != NULL)) {
			/*
			 * found under a different name
			 */
			match = 1;
			tp = other;
		}
		if (!match || (tuid != (uid_t)-1 && tuid != psinfo->pr_euid)) {
			/*
			 * not found OR not matching euid
			 */
			*tpp = tp;
			return (0);
		}
	}
	*tpp = tp;
	return (1);
}

/*
 * Print info about the process.
 */
static void
prcom(psinfo_t *psinfo, char *ttyp)
{
	char	*cp;
	long	tm;
	int	bytesleft;
	int	wcnt, length;
	wchar_t	wchar;
	struct passwd *pwd;
	int	zombie_lwp;
	char	zonename[ZONENAME_MAX];

	/*
	 * If process is zombie, call zombie print routine and return.
	 */
	if (psinfo->pr_nlwp == 0) {
		if (fields != NULL)
			pr_fields(psinfo, ttyp, print_zombie_field);
		else
			przom(psinfo);
		return;
	}

	zombie_lwp = (Lflg && psinfo->pr_lwp.pr_sname == 'Z');

	/*
	 * If user specified '-o format', print requested fields and return.
	 */
	if (fields != NULL) {
		pr_fields(psinfo, ttyp, print_field);
		return;
	}

	/*
	 * All fields before 'PID' are printed with a trailing space as a
	 * separator, rather than keeping track of which column is first.  All
	 * other fields are printed with a leading space.
	 */
	if (lflg) {
		if (!yflg)
			(void) printf("%2x ", psinfo->pr_flag & 0377); /* F */
		(void) printf("%c ", psinfo->pr_lwp.pr_sname);	/* S */
	}

	if (Zflg) {						/* ZONE */
		if (getzonenamebyid(psinfo->pr_zoneid, zonename,
		    sizeof (zonename)) < 0) {
			if (snprintf(NULL, 0, "%d",
			    ((int)psinfo->pr_zoneid)) > 7)
				(void) printf(" %6.6d%c ",
				    ((int)psinfo->pr_zoneid), '*');
			else
				(void) printf(" %7.7d ",
				    ((int)psinfo->pr_zoneid));
		} else {
			size_t nw;

			nw = mbstowcs(NULL, zonename, 0);
			if (nw == (size_t)-1)
				(void) printf("%8.8s ", "ERROR");
			else if (nw > 8)
				(void) wprintf(L"%7.7s%c ", zonename, '*');
			else
				(void) wprintf(L"%8.8s ", zonename);
		}
	}

	if (fflg) {						/* UID */
		if ((pwd = getpwuid(psinfo->pr_euid)) != NULL) {
			size_t nw;

			nw = mbstowcs(NULL, pwd->pw_name, 0);
			if (nw == (size_t)-1)
				(void) printf("%8.8s ", "ERROR");
			else if (nw > 8)
				(void) wprintf(L"%7.7s%c ", pwd->pw_name, '*');
			else
				(void) wprintf(L"%8.8s ", pwd->pw_name);
		} else {
			if (snprintf(NULL, 0, "%u",
			    (psinfo->pr_euid)) > 7)
				(void) printf(" %6.6u%c ", psinfo->pr_euid,
				    '*');
			else
				(void) printf(" %7.7u ", psinfo->pr_euid);
		}
	} else if (lflg) {
		if (snprintf(NULL, 0, "%u", (psinfo->pr_euid)) > 6)
			(void) printf("%5.5u%c ", psinfo->pr_euid, '*');
		else
			(void) printf("%6u ", psinfo->pr_euid);
	}
	(void) printf("%*d", pidwidth, (int)psinfo->pr_pid); /* PID */
	if (lflg || fflg)
		(void) printf(" %*d", pidwidth,
		    (int)psinfo->pr_ppid); /* PPID */
	if (jflg) {
		(void) printf(" %*d", pidwidth,
		    (int)psinfo->pr_pgid);	/* PGID */
		(void) printf(" %*d", pidwidth,
		    (int)psinfo->pr_sid);	/* SID  */
	}
	if (Lflg)
		(void) printf(" %5d", (int)psinfo->pr_lwp.pr_lwpid); /* LWP */
	if (Pflg) {
		if (psinfo->pr_lwp.pr_bindpro == PBIND_NONE)	/* PSR */
			(void) printf("   -");
		else
			(void) printf(" %3d", psinfo->pr_lwp.pr_bindpro);
	}
	if (Lflg && fflg)					/* NLWP */
		(void) printf(" %5d", psinfo->pr_nlwp + psinfo->pr_nzomb);
	if (cflg) {
		if (zombie_lwp)					/* CLS */
			(void) printf("     ");
		else
			(void) printf(" %4s", psinfo->pr_lwp.pr_clname);
		(void) printf(" %3d", psinfo->pr_lwp.pr_pri);	/* PRI */
	} else if (lflg || fflg) {
		(void) printf(" %3d", psinfo->pr_lwp.pr_cpu & 0377); /* C   */
		if (lflg) {					    /* PRI NI */
			/*
			 * Print priorities the old way (lower numbers
			 * mean higher priority) and print nice value
			 * for time sharing procs.
			 */
			(void) printf(" %3d", psinfo->pr_lwp.pr_oldpri);
			if (psinfo->pr_lwp.pr_oldpri != 0)
				(void) printf(" %2d", psinfo->pr_lwp.pr_nice);
			else
				(void) printf(" %2.2s",
				    psinfo->pr_lwp.pr_clname);
		}
	}
	if (lflg) {
		if (yflg) {
			if (psinfo->pr_flag & SSYS)		/* RSS */
				(void) printf("     0");
			else if (psinfo->pr_rssize)
				(void) printf(" %5lu",
				    (ulong_t)psinfo->pr_rssize);
			else
				(void) printf("     ?");
			if (psinfo->pr_flag & SSYS)		/* SZ */
				(void) printf("      0");
			else if (psinfo->pr_size)
				(void) printf(" %6lu",
				    (ulong_t)psinfo->pr_size);
			else
				(void) printf("      ?");
		} else {
#ifndef _LP64
			if (psinfo->pr_addr)			/* ADDR */
				(void) printf(" %8lx",
				    (ulong_t)psinfo->pr_addr);
			else
#endif
				(void) printf("        ?");
			if (psinfo->pr_flag & SSYS)		/* SZ */
				(void) printf("      0");
			else if (psinfo->pr_size)
				(void) printf(" %6lu",
				    (ulong_t)psinfo->pr_size / kbytes_per_page);
			else
				(void) printf("      ?");
		}
		if (psinfo->pr_lwp.pr_sname != 'S')		/* WCHAN */
			(void) printf("         ");
#ifndef _LP64
		else if (psinfo->pr_lwp.pr_wchan)
			(void) printf(" %8lx",
			    (ulong_t)psinfo->pr_lwp.pr_wchan);
#endif
		else
			(void) printf("        ?");
	}
	if (fflg) {						/* STIME */
		int width = fname[F_STIME].width;
		if (Lflg)
			prtime(psinfo->pr_lwp.pr_start, width + 1, 1);
		else
			prtime(psinfo->pr_start, width + 1, 1);
	}

	if (Hflg) {
		/* Display home lgroup */
		(void) printf(" %4d", (int)psinfo->pr_lwp.pr_lgrp);
	}

	(void) printf(" %-8.14s", ttyp);			/* TTY */
	if (Lflg) {
		tm = psinfo->pr_lwp.pr_time.tv_sec;
		if (psinfo->pr_lwp.pr_time.tv_nsec > 500000000)
			tm++;
	} else {
		tm = psinfo->pr_time.tv_sec;
		if (psinfo->pr_time.tv_nsec > 500000000)
			tm++;
	}
	(void) printf(" %4ld:%.2ld", tm / 60, tm % 60);		/* [L]TIME */

	if (zombie_lwp) {
		(void) printf(" <defunct>\n");
		return;
	}

	if (!fflg) {						/* CMD */
		wcnt = namencnt(psinfo->pr_fname, 16, 8);
		(void) printf(" %.*s\n", wcnt, psinfo->pr_fname);
		return;
	}


	/*
	 * PRARGSZ == length of cmd arg string.
	 */
	psinfo->pr_psargs[PRARGSZ-1] = '\0';
	bytesleft = PRARGSZ;
	for (cp = psinfo->pr_psargs; *cp != '\0'; cp += length) {
		length = mbtowc(&wchar, cp, MB_LEN_MAX);
		if (length == 0)
			break;
		if (length < 0 || !iswprint(wchar)) {
			if (length < 0)
				length = 1;
			if (bytesleft <= length) {
				*cp = '\0';
				break;
			}
			/* omit the unprintable character */
			(void) memmove(cp, cp+length, bytesleft-length);
			length = 0;
		}
		bytesleft -= length;
	}
	wcnt = namencnt(psinfo->pr_psargs, PRARGSZ, lflg ? 35 : PRARGSZ);
	(void) printf(" %.*s\n", wcnt, psinfo->pr_psargs);
}

/*
 * Print percent from 16-bit binary fraction [0 .. 1]
 * Round up .01 to .1 to indicate some small percentage (the 0x7000 below).
 */
static void
prtpct(ushort_t pct, int width)
{
	uint_t value = pct;	/* need 32 bits to compute with */

	value = ((value * 1000) + 0x7000) >> 15;	/* [0 .. 1000] */
	if (value >= 1000)
		value = 999;
	if ((width -= 2) < 2)
		width = 2;
	(void) printf("%*u.%u", width, value / 10, value % 10);
}

static void
print_time(time_t tim, int width)
{
	char buf[30];
	time_t seconds;
	time_t minutes;
	time_t hours;
	time_t days;

	if (tim < 0) {
		(void) printf("%*s", width, "-");
		return;
	}

	seconds = tim % 60;
	tim /= 60;
	minutes = tim % 60;
	tim /= 60;
	hours = tim % 24;
	days = tim / 24;

	if (days > 0) {
		(void) snprintf(buf, sizeof (buf), "%ld-%2.2ld:%2.2ld:%2.2ld",
		    days, hours, minutes, seconds);
	} else if (hours > 0) {
		(void) snprintf(buf, sizeof (buf), "%2.2ld:%2.2ld:%2.2ld",
		    hours, minutes, seconds);
	} else {
		(void) snprintf(buf, sizeof (buf), "%2.2ld:%2.2ld",
		    minutes, seconds);
	}

	(void) printf("%*s", width, buf);
}

static void
print_field(psinfo_t *psinfo, struct field *f, const char *ttyp)
{
	int width = f->width;
	struct passwd *pwd;
	struct group *grp;
	time_t cputime;
	int bytesleft;
	int wcnt;
	wchar_t	wchar;
	char *cp;
	int length;
	ulong_t mask;
	char c = '\0', *csave = NULL;
	int zombie_lwp;

	zombie_lwp = (Lflg && psinfo->pr_lwp.pr_sname == 'Z');

	switch (f->fname) {
	case F_RUSER:
		if ((pwd = getpwuid(psinfo->pr_uid)) != NULL) {
			size_t nw;

			nw = mbstowcs(NULL, pwd->pw_name, 0);
			if (nw == (size_t)-1)
				(void) printf("%*s ", width, "ERROR");
			else if (Wflg && nw > width)
				(void) wprintf(L"%.*s%c", width - 1,
				    pwd->pw_name, '*');
			else
				(void) wprintf(L"%*s", width, pwd->pw_name);
		} else {
			if (Wflg && snprintf(NULL, 0, "%u",
			    (psinfo->pr_uid)) > width)

				(void) printf("%*u%c", width - 1,
				    psinfo->pr_uid, '*');
			else
				(void) printf("%*u", width, psinfo->pr_uid);
		}
		break;
	case F_USER:
		if ((pwd = getpwuid(psinfo->pr_euid)) != NULL) {
			size_t nw;

			nw = mbstowcs(NULL, pwd->pw_name, 0);
			if (nw == (size_t)-1)
				(void) printf("%*s ", width, "ERROR");
			else if (Wflg && nw > width)
				(void) wprintf(L"%.*s%c", width - 1,
				    pwd->pw_name, '*');
			else
				(void) wprintf(L"%*s", width, pwd->pw_name);
		} else {
			if (Wflg && snprintf(NULL, 0, "%u",
			    (psinfo->pr_euid)) > width)

				(void) printf("%*u%c", width - 1,
				    psinfo->pr_euid, '*');
			else
				(void) printf("%*u", width, psinfo->pr_euid);
		}
		break;
	case F_RGROUP:
		if ((grp = getgrgid(psinfo->pr_gid)) != NULL)
			(void) printf("%*s", width, grp->gr_name);
		else
			(void) printf("%*u", width, psinfo->pr_gid);
		break;
	case F_GROUP:
		if ((grp = getgrgid(psinfo->pr_egid)) != NULL)
			(void) printf("%*s", width, grp->gr_name);
		else
			(void) printf("%*u", width, psinfo->pr_egid);
		break;
	case F_RUID:
		(void) printf("%*u", width, psinfo->pr_uid);
		break;
	case F_UID:
		(void) printf("%*u", width, psinfo->pr_euid);
		break;
	case F_RGID:
		(void) printf("%*u", width, psinfo->pr_gid);
		break;
	case F_GID:
		(void) printf("%*u", width, psinfo->pr_egid);
		break;
	case F_PID:
		(void) printf("%*d", width, (int)psinfo->pr_pid);
		break;
	case F_PPID:
		(void) printf("%*d", width, (int)psinfo->pr_ppid);
		break;
	case F_PGID:
		(void) printf("%*d", width, (int)psinfo->pr_pgid);
		break;
	case F_SID:
		(void) printf("%*d", width, (int)psinfo->pr_sid);
		break;
	case F_PSR:
		if (zombie_lwp || psinfo->pr_lwp.pr_bindpro == PBIND_NONE)
			(void) printf("%*s", width, "-");
		else
			(void) printf("%*d", width, psinfo->pr_lwp.pr_bindpro);
		break;
	case F_LWP:
		(void) printf("%*d", width, (int)psinfo->pr_lwp.pr_lwpid);
		break;
	case F_NLWP:
		(void) printf("%*d", width, psinfo->pr_nlwp + psinfo->pr_nzomb);
		break;
	case F_OPRI:
		if (zombie_lwp)
			(void) printf("%*s", width, "-");
		else
			(void) printf("%*d", width, psinfo->pr_lwp.pr_oldpri);
		break;
	case F_PRI:
		if (zombie_lwp)
			(void) printf("%*s", width, "-");
		else
			(void) printf("%*d", width, psinfo->pr_lwp.pr_pri);
		break;
	case F_F:
		mask = 0xffffffffUL;
		if (width < 8)
			mask >>= (8 - width) * 4;
		(void) printf("%*lx", width, psinfo->pr_flag & mask);
		break;
	case F_S:
		(void) printf("%*c", width, psinfo->pr_lwp.pr_sname);
		break;
	case F_C:
		if (zombie_lwp)
			(void) printf("%*s", width, "-");
		else
			(void) printf("%*d", width, psinfo->pr_lwp.pr_cpu);
		break;
	case F_PCPU:
		if (zombie_lwp)
			(void) printf("%*s", width, "-");
		else if (Lflg)
			prtpct(psinfo->pr_lwp.pr_pctcpu, width);
		else
			prtpct(psinfo->pr_pctcpu, width);
		break;
	case F_PMEM:
		prtpct(psinfo->pr_pctmem, width);
		break;
	case F_OSZ:
		(void) printf("%*lu", width,
		    (ulong_t)psinfo->pr_size / kbytes_per_page);
		break;
	case F_VSZ:
		(void) printf("%*lu", width, (ulong_t)psinfo->pr_size);
		break;
	case F_RSS:
		(void) printf("%*lu", width, (ulong_t)psinfo->pr_rssize);
		break;
	case F_NICE:
		/* if pr_oldpri is zero, then this class has no nice */
		if (zombie_lwp)
			(void) printf("%*s", width, "-");
		else if (psinfo->pr_lwp.pr_oldpri != 0)
			(void) printf("%*d", width, psinfo->pr_lwp.pr_nice);
		else
			(void) printf("%*.*s", width, width,
			    psinfo->pr_lwp.pr_clname);
		break;
	case F_CLASS:
		if (zombie_lwp)
			(void) printf("%*s", width, "-");
		else
			(void) printf("%*.*s", width, width,
			    psinfo->pr_lwp.pr_clname);
		break;
	case F_STIME:
		if (Lflg)
			prtime(psinfo->pr_lwp.pr_start, width, 0);
		else
			prtime(psinfo->pr_start, width, 0);
		break;
	case F_ETIME:
		if (Lflg)
			print_time(delta_secs(&psinfo->pr_lwp.pr_start),
			    width);
		else
			print_time(delta_secs(&psinfo->pr_start), width);
		break;
	case F_TIME:
		if (Lflg) {
			cputime = psinfo->pr_lwp.pr_time.tv_sec;
			if (psinfo->pr_lwp.pr_time.tv_nsec > 500000000)
				cputime++;
		} else {
			cputime = psinfo->pr_time.tv_sec;
			if (psinfo->pr_time.tv_nsec > 500000000)
				cputime++;
		}
		print_time(cputime, width);
		break;
	case F_TTY:
		(void) printf("%-*s", width, ttyp);
		break;
	case F_ADDR:
		if (zombie_lwp)
			(void) printf("%*s", width, "-");
		else if (Lflg)
			(void) printf("%*lx", width,
			    (long)psinfo->pr_lwp.pr_addr);
		else
			(void) printf("%*lx", width, (long)psinfo->pr_addr);
		break;
	case F_WCHAN:
		if (!zombie_lwp && psinfo->pr_lwp.pr_wchan)
			(void) printf("%*lx", width,
			    (long)psinfo->pr_lwp.pr_wchan);
		else
			(void) printf("%*.*s", width, width, "-");
		break;
	case F_FNAME:
		/*
		 * Print full width unless this is the last output format.
		 */
		if (zombie_lwp) {
			if (f->next != NULL)
				(void) printf("%-*s", width, "<defunct>");
			else
				(void) printf("%s", "<defunct>");
			break;
		}
		wcnt = namencnt(psinfo->pr_fname, 16, width);
		if (f->next != NULL)
			(void) printf("%-*.*s", width, wcnt, psinfo->pr_fname);
		else
			(void) printf("%-.*s", wcnt, psinfo->pr_fname);
		break;
	case F_COMM:
		if (zombie_lwp) {
			if (f->next != NULL)
				(void) printf("%-*s", width, "<defunct>");
			else
				(void) printf("%s", "<defunct>");
			break;
		}
		csave = strpbrk(psinfo->pr_psargs, " \t\r\v\f\n");
		if (csave) {
			c = *csave;
			*csave = '\0';
		}
		/* FALLTHROUGH */
	case F_ARGS:
		/*
		 * PRARGSZ == length of cmd arg string.
		 */
		if (zombie_lwp) {
			(void) printf("%-*s", width, "<defunct>");
			break;
		}
		psinfo->pr_psargs[PRARGSZ-1] = '\0';
		bytesleft = PRARGSZ;
		for (cp = psinfo->pr_psargs; *cp != '\0'; cp += length) {
			length = mbtowc(&wchar, cp, MB_LEN_MAX);
			if (length == 0)
				break;
			if (length < 0 || !iswprint(wchar)) {
				if (length < 0)
					length = 1;
				if (bytesleft <= length) {
					*cp = '\0';
					break;
				}
				/* omit the unprintable character */
				(void) memmove(cp, cp+length, bytesleft-length);
				length = 0;
			}
			bytesleft -= length;
		}
		wcnt = namencnt(psinfo->pr_psargs, PRARGSZ, width);
		/*
		 * Print full width unless this is the last format.
		 */
		if (f->next != NULL)
			(void) printf("%-*.*s", width, wcnt,
			    psinfo->pr_psargs);
		else
			(void) printf("%-.*s", wcnt,
			    psinfo->pr_psargs);
		if (f->fname == F_COMM && csave)
			*csave = c;
		break;
	case F_TASKID:
		(void) printf("%*d", width, (int)psinfo->pr_taskid);
		break;
	case F_PROJID:
		(void) printf("%*d", width, (int)psinfo->pr_projid);
		break;
	case F_PROJECT:
		{
			struct project cproj;
			char proj_buf[PROJECT_BUFSZ];

			if ((getprojbyid(psinfo->pr_projid, &cproj,
			    (void *)&proj_buf, PROJECT_BUFSZ)) == NULL) {
				if (Wflg && snprintf(NULL, 0, "%d",
				    ((int)psinfo->pr_projid)) > width)
					(void) printf("%.*d%c", width - 1,
					    ((int)psinfo->pr_projid), '*');
				else
					(void) printf("%*d", width,
					    (int)psinfo->pr_projid);
			} else {
				size_t nw;

				if (cproj.pj_name != NULL)
					nw = mbstowcs(NULL, cproj.pj_name, 0);
				if (cproj.pj_name == NULL)
					(void) printf("%*s ", width, "---");
				else if (nw == (size_t)-1)
					(void) printf("%*s ", width, "ERROR");
				else if (Wflg && nw > width)
					(void) wprintf(L"%.*s%c", width - 1,
					    cproj.pj_name, '*');
				else
					(void) wprintf(L"%*s", width,
					    cproj.pj_name);
			}
		}
		break;
	case F_PSET:
		if (zombie_lwp || psinfo->pr_lwp.pr_bindpset == PS_NONE)
			(void) printf("%*s", width, "-");
		else
			(void) printf("%*d", width, psinfo->pr_lwp.pr_bindpset);
		break;
	case F_ZONEID:
		(void) printf("%*d", width, (int)psinfo->pr_zoneid);
		break;
	case F_ZONE:
		{
			char zonename[ZONENAME_MAX];

			if (getzonenamebyid(psinfo->pr_zoneid, zonename,
			    sizeof (zonename)) < 0) {
				if (Wflg && snprintf(NULL, 0, "%d",
				    ((int)psinfo->pr_zoneid)) > width)
					(void) printf("%.*d%c", width - 1,
					    ((int)psinfo->pr_zoneid), '*');
				else
					(void) printf("%*d", width,
					    (int)psinfo->pr_zoneid);
			} else {
				size_t nw;

				nw = mbstowcs(NULL, zonename, 0);
				if (nw == (size_t)-1)
					(void) printf("%*s ", width, "ERROR");
				else if (Wflg && nw > width)
					(void) wprintf(L"%.*s%c", width - 1,
					    zonename, '*');
				else
					(void) wprintf(L"%*s", width, zonename);
			}
		}
		break;
	case F_CTID:
		if (psinfo->pr_contract == -1)
			(void) printf("%*s", width, "-");
		else
			(void) printf("%*ld", width, (long)psinfo->pr_contract);
		break;
	case F_LGRP:
		/* Display home lgroup */
		(void) printf("%*d", width, (int)psinfo->pr_lwp.pr_lgrp);
		break;

	case F_DMODEL:
		(void) printf("%*s", width,
		    psinfo->pr_dmodel == PR_MODEL_LP64 ? "_LP64" : "_ILP32");
		break;
	}
}

static void
print_zombie_field(psinfo_t *psinfo, struct field *f, const char *ttyp)
{
	int wcnt;
	int width = f->width;

	switch (f->fname) {
	case F_FNAME:
	case F_COMM:
	case F_ARGS:
		/*
		 * Print full width unless this is the last output format.
		 */
		wcnt = min(width, sizeof ("<defunct>"));
		if (f->next != NULL)
			(void) printf("%-*.*s", width, wcnt, "<defunct>");
		else
			(void) printf("%-.*s", wcnt, "<defunct>");
		break;

	case F_PSR:
	case F_PCPU:
	case F_PMEM:
	case F_NICE:
	case F_CLASS:
	case F_STIME:
	case F_ETIME:
	case F_WCHAN:
	case F_PSET:
		(void) printf("%*s", width, "-");
		break;

	case F_OPRI:
	case F_PRI:
	case F_OSZ:
	case F_VSZ:
	case F_RSS:
		(void) printf("%*d", width, 0);
		break;

	default:
		print_field(psinfo, f, ttyp);
		break;
	}
}

static void
pr_fields(psinfo_t *psinfo, const char *ttyp,
	void (*print_fld)(psinfo_t *, struct field *, const char *))
{
	struct field *f;

	for (f = fields; f != NULL; f = f->next) {
		print_fld(psinfo, f, ttyp);
		if (f->next != NULL)
			(void) printf(" ");
	}
	(void) printf("\n");
}

/*
 * Returns 1 if arg is found in array arr, of length num; 0 otherwise.
 */
static int
search(pid_t *arr, int number, pid_t arg)
{
	int i;

	for (i = 0; i < number; i++)
		if (arg == arr[i])
			return (1);
	return (0);
}

/*
 * Add an entry (user, group) to the specified table.
 */
static void
add_ugentry(struct ughead *tbl, char *name)
{
	struct ugdata *entp;

	if (tbl->size == tbl->nent) {	/* reallocate the table entries */
		if ((tbl->size *= 2) == 0)
			tbl->size = 32;		/* first time */
		tbl->ent = Realloc(tbl->ent, tbl->size*sizeof (struct ugdata));
	}
	entp = &tbl->ent[tbl->nent++];
	entp->id = 0;
	(void) strncpy(entp->name, name, MAXUGNAME);
	entp->name[MAXUGNAME] = '\0';
}

static int
uconv(struct ughead *uhead)
{
	struct ugdata *utbl = uhead->ent;
	int n = uhead->nent;
	struct passwd *pwd;
	int i;
	int fnd = 0;
	uid_t uid;

	/*
	 * Ask the name service for names.
	 */
	for (i = 0; i < n; i++) {
		/*
		 * If name is numeric, ask for numeric id
		 */
		if (str2uid(utbl[i].name, &uid, 0, MAXEPHUID) == 0)
			pwd = getpwuid(uid);
		else
			pwd = getpwnam(utbl[i].name);

		/*
		 * If found, enter found index into tbl array.
		 */
		if (pwd == NULL) {
			(void) fprintf(stderr,
			    gettext("ps: unknown user %s\n"), utbl[i].name);
			continue;
		}

		utbl[fnd].id = pwd->pw_uid;
		(void) strncpy(utbl[fnd].name, pwd->pw_name, MAXUGNAME);
		fnd++;
	}

	uhead->nent = fnd;	/* in case it changed */
	return (n - fnd);
}

static int
gconv(struct ughead *ghead)
{
	struct ugdata *gtbl = ghead->ent;
	int n = ghead->nent;
	struct group *grp;
	gid_t gid;
	int i;
	int fnd = 0;

	/*
	 * Ask the name service for names.
	 */
	for (i = 0; i < n; i++) {
		/*
		 * If name is numeric, ask for numeric id
		 */
		if (str2uid(gtbl[i].name, (uid_t *)&gid, 0, MAXEPHUID) == 0)
			grp = getgrgid(gid);
		else
			grp = getgrnam(gtbl[i].name);
		/*
		 * If found, enter found index into tbl array.
		 */
		if (grp == NULL) {
			(void) fprintf(stderr,
			    gettext("ps: unknown group %s\n"), gtbl[i].name);
			continue;
		}

		gtbl[fnd].id = grp->gr_gid;
		(void) strncpy(gtbl[fnd].name, grp->gr_name, MAXUGNAME);
		fnd++;
	}

	ghead->nent = fnd;	/* in case it changed */
	return (n - fnd);
}

/*
 * Return 1 if puid is in table, otherwise 0.
 */
static int
ugfind(id_t id, struct ughead *ughead)
{
	struct ugdata *utbl = ughead->ent;
	int n = ughead->nent;
	int i;

	for (i = 0; i < n; i++)
		if (utbl[i].id == id)
			return (1);
	return (0);
}

/*
 * Print starting time of process unless process started more than 24 hours
 * ago, in which case the date is printed.  The date is printed in the form
 * "MMM dd" if old format, else the blank is replaced with an '_' so
 * it appears as a single word (for parseability).
 */
static void
prtime(timestruc_t st, int width, int old)
{
	char sttim[26];
	time_t starttime;

	starttime = st.tv_sec;
	if (st.tv_nsec > 500000000)
		starttime++;
	if ((now.tv_sec - starttime) >= 24*60*60) {
		(void) strftime(sttim, sizeof (sttim), old?
		/*
		 * TRANSLATION_NOTE
		 * This time format is used by STIME field when -f option
		 * is specified.  Used for processes that begun more than
		 * 24 hours.
		 */
		    dcgettext(NULL, "%b %d", LC_TIME) :
		/*
		 * TRANSLATION_NOTE
		 * This time format is used by STIME field when -o option
		 * is specified.  Used for processes that begun more than
		 * 24 hours.
		 */
		    dcgettext(NULL, "%b_%d", LC_TIME), localtime(&starttime));
	} else {
		/*
		 * TRANSLATION_NOTE
		 * This time format is used by STIME field when -f or -o option
		 * is specified.  Used for processes that begun less than
		 * 24 hours.
		 */
		(void) strftime(sttim, sizeof (sttim),
		    dcgettext(NULL, "%H:%M:%S", LC_TIME),
		    localtime(&starttime));
	}
	(void) printf("%*.*s", width, width, sttim);
}

static void
przom(psinfo_t *psinfo)
{
	long	tm;
	struct passwd *pwd;
	char zonename[ZONENAME_MAX];

	/*
	 * All fields before 'PID' are printed with a trailing space as a
	 * spearator, rather than keeping track of which column is first.  All
	 * other fields are printed with a leading space.
	 */
	if (lflg) {	/* F S */
		if (!yflg)
			(void) printf("%2x ", psinfo->pr_flag & 0377); /* F */
		(void) printf("%c ", psinfo->pr_lwp.pr_sname);	/* S */
	}
	if (Zflg) {
		if (getzonenamebyid(psinfo->pr_zoneid, zonename,
		    sizeof (zonename)) < 0) {
			if (snprintf(NULL, 0, "%d",
			    ((int)psinfo->pr_zoneid)) > 7)
				(void) printf(" %6.6d%c ",
				    ((int)psinfo->pr_zoneid), '*');
			else
				(void) printf(" %7.7d ",
				    ((int)psinfo->pr_zoneid));
		} else {
			size_t nw;

			nw = mbstowcs(NULL, zonename, 0);
			if (nw == (size_t)-1)
				(void) printf("%8.8s ", "ERROR");
			else if (nw > 8)
				(void) wprintf(L"%7.7s%c ", zonename, '*');
			else
				(void) wprintf(L"%8.8s ", zonename);
		}
	}
	if (Hflg) {
		/* Display home lgroup */
		(void) printf(" %6d", (int)psinfo->pr_lwp.pr_lgrp); /* LGRP */
	}
	if (fflg) {
		if ((pwd = getpwuid(psinfo->pr_euid)) != NULL) {
			size_t nw;

			nw = mbstowcs(NULL, pwd->pw_name, 0);
			if (nw == (size_t)-1)
				(void) printf("%8.8s ", "ERROR");
			else if (nw > 8)
				(void) wprintf(L"%7.7s%c ", pwd->pw_name, '*');
			else
				(void) wprintf(L"%8.8s ", pwd->pw_name);
		} else {
			if (snprintf(NULL, 0, "%u",
			    (psinfo->pr_euid)) > 7)
				(void) printf(" %6.6u%c ", psinfo->pr_euid,
				    '*');
			else
				(void) printf(" %7.7u ", psinfo->pr_euid);
		}
	} else if (lflg) {
		if (snprintf(NULL, 0, "%u", (psinfo->pr_euid)) > 6)
			(void) printf("%5.5u%c ", psinfo->pr_euid, '*');
		else
			(void) printf("%6u ", psinfo->pr_euid);
	}

	(void) printf("%*d", pidwidth, (int)psinfo->pr_pid);	/* PID */
	if (lflg || fflg)
		(void) printf(" %*d", pidwidth,
		    (int)psinfo->pr_ppid);			/* PPID */

	if (jflg) {
		(void) printf(" %*d", pidwidth,
		    (int)psinfo->pr_pgid);			/* PGID */
		(void) printf(" %*d", pidwidth,
		    (int)psinfo->pr_sid);			/* SID  */
	}

	if (Lflg)
		(void) printf(" %5d", 0);			/* LWP */
	if (Pflg)
		(void) printf("   -");				/* PSR */
	if (Lflg && fflg)
		(void) printf(" %5d", 0);			/* NLWP */

	if (cflg) {
		(void) printf(" %4s", "-");	/* zombies have no class */
		(void) printf(" %3d", psinfo->pr_lwp.pr_pri);	/* PRI	*/
	} else if (lflg || fflg) {
		(void) printf(" %3d", psinfo->pr_lwp.pr_cpu & 0377); /* C   */
		if (lflg)
			(void) printf(" %3d %2s",
			    psinfo->pr_lwp.pr_oldpri, "-");	/* PRI NI */
	}
	if (lflg) {
		if (yflg)				/* RSS SZ WCHAN */
			(void) printf(" %5d %6d %8s", 0, 0, "-");
		else					/* ADDR SZ WCHAN */
			(void) printf(" %8s %6d %8s", "-", 0, "-");
	}
	if (fflg) {
		int width = fname[F_STIME].width;
		(void) printf(" %*.*s", width, width, "-"); 	/* STIME */
	}
	(void) printf(" %-8.14s", "?");				/* TTY */

	tm = psinfo->pr_time.tv_sec;
	if (psinfo->pr_time.tv_nsec > 500000000)
		tm++;
	(void) printf(" %4ld:%.2ld", tm / 60, tm % 60);	/* TIME */
	(void) printf(" <defunct>\n");
}

/*
 * Function to compute the number of printable bytes in a multibyte
 * command string ("internationalization").
 */
static int
namencnt(char *cmd, int csisize, int scrsize)
{
	int csiwcnt = 0, scrwcnt = 0;
	int ncsisz, nscrsz;
	wchar_t  wchar;
	int	 len;

	while (*cmd != '\0') {
		if ((len = csisize - csiwcnt) > (int)MB_CUR_MAX)
			len = MB_CUR_MAX;
		if ((ncsisz = mbtowc(&wchar, cmd, len)) < 0)
			return (8); /* default to use for illegal chars */
		if ((nscrsz = wcwidth(wchar)) <= 0)
			return (8);
		if (csiwcnt + ncsisz > csisize || scrwcnt + nscrsz > scrsize)
			break;
		csiwcnt += ncsisz;
		scrwcnt += nscrsz;
		cmd += ncsisz;
	}
	return (csiwcnt);
}

static char *
err_string(int err)
{
	static char buf[32];
	char *str = strerror(err);

	if (str == NULL)
		(void) snprintf(str = buf, sizeof (buf), "Errno #%d", err);

	return (str);
}

/* If allocation fails, die */
static void *
Realloc(void *ptr, size_t size)
{
	ptr = realloc(ptr, size);
	if (ptr == NULL) {
		(void) fprintf(stderr, gettext("ps: no memory\n"));
		exit(1);
	}
	return (ptr);
}

static time_t
delta_secs(const timestruc_t *start)
{
	time_t seconds = now.tv_sec - start->tv_sec;
	long nanosecs = now.tv_usec * 1000 - start->tv_nsec;

	if (nanosecs >= (NANOSEC / 2))
		seconds++;
	else if (nanosecs < -(NANOSEC / 2))
		seconds--;

	return (seconds);
}

/*
 * Returns the following:
 *
 * 	0	No error
 * 	EINVAL	Invalid number
 * 	ERANGE	Value exceeds (min, max) range
 */
static int
str2id(const char *p, pid_t *val, long min, long max)
{
	char *q;
	long number;
	int error;

	errno = 0;
	number = strtol(p, &q, 10);

	if (errno != 0 || q == p || *q != '\0') {
		if ((error = errno) == 0) {
			/*
			 * strtol() can fail without setting errno, or it can
			 * set it to EINVAL or ERANGE.  In the case errno is
			 * still zero, return EINVAL.
			 */
			error = EINVAL;
		}
	} else if (number < min || number > max) {
		error = ERANGE;
	} else {
		error = 0;
	}

	*val = number;

	return (error);
}

/*
 * Returns the following:
 *
 * 	0	No error
 * 	EINVAL	Invalid number
 * 	ERANGE	Value exceeds (min, max) range
 */
static int
str2uid(const char *p, uid_t *val, unsigned long min, unsigned long max)
{
	char *q;
	unsigned long number;
	int error;

	errno = 0;
	number = strtoul(p, &q, 10);

	if (errno != 0 || q == p || *q != '\0') {
		if ((error = errno) == 0) {
			/*
			 * strtoul() can fail without setting errno, or it can
			 * set it to EINVAL or ERANGE.  In the case errno is
			 * still zero, return EINVAL.
			 */
			error = EINVAL;
		}
	} else if (number < min || number > max) {
		error = ERANGE;
	} else {
		error = 0;
	}

	*val = number;

	return (error);
}

static int
pidcmp(const void *p1, const void *p2)
{
	pid_t i = *((pid_t *)p1);
	pid_t j = *((pid_t *)p2);

	return (i - j);
}