/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * The plgrp utility allows a user to display and modify the home lgroup and
 * lgroup affinities of the specified threads
 */

#include <ctype.h>
#include <errno.h>
#include <libintl.h>
#include <libproc.h>
#include <locale.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <libgen.h>
#include <sys/lgrp_user.h>


/*
 * Delimiters
 */
#define	DELIMIT_AFF	'/'	/* lgroup affinity from lgroups */
#define	DELIMIT_LGRP	","	/* lgroups from each other */
#define	DELIMIT_LWP	"/"	/* thread/LWP IDs from process ID */
#define	DELIMIT_RANGE	'-'	/* range of IDs (eg. lgroup) */
#define	DELIMIT_AFF_LST ','	/* list of affinities from another list */

/*
 * Exit values other than EXIT_{SUCCESS,FAILURE}
 */
#define	EXIT_NONFATAL 2		/* non-fatal errors */

/*
 * Header and format strings
 */
#define	HDR_PLGRP_AFF_GET	"     PID/LWPID    HOME  AFFINITY\n"
#define	HDR_PLGRP_AFF_SET	"     PID/LWPID    HOME       AFFINITY\n"
#define	HDR_PLGRP_HOME_GET	"     PID/LWPID    HOME\n"
#define	HDR_PLGRP_HOME_SET	"     PID/LWPID    HOME\n"

/*
 * Part of the HDR_PLGRP_AFF_SET header used to calculate space needed to
 * represent changing home as old => new
 */
#define	HDR_PLGRP_HOME_CHANGE	"HOME       "

#define	FMT_AFF			"%d/%s"
#define	FMT_AFF_STR		"%s"
#define	FMT_HOME		"%-6d"
#define	FMT_NEWHOME		"%d => %d"
#define	FMT_THREAD		"%8d/%-8d"

/*
 * How much to allocate for lgroup bitmap array as it grows
 */
#define	LGRP_BITMAP_CHUNK 8

/*
 * Strings that can be given for lgroups
 */
#define	LGRP_ALL_STR		"all"
#define	LGRP_LEAVES_STR		"leaves"
#define	LGRP_ROOT_STR		"root"

/*
 * Strings corresponding to lgroup affinities
 */
#define	LGRP_AFF_NONE_STR	"none"
#define	LGRP_AFF_STRONG_STR	"strong"
#define	LGRP_AFF_WEAK_STR	"weak"

/*
 * Invalid value for lgroup affinity
 */
#define	LGRP_AFF_INVALID	-1

/*
 * Number of args needed for lgroup system call
 */
#define	LGRPSYS_NARGS		3

#ifndef	TEXT_DOMAIN			/* should be defined by cc -D */
#define	TEXT_DOMAIN	"SYS_TEST"	/* use this only if it wasn't */
#endif

/*
 * plgrp(1) operations
 */
typedef enum plgrp_ops {
	PLGRP_AFFINITY_GET,
	PLGRP_AFFINITY_SET,
	PLGRP_HOME_GET,
	PLGRP_HOME_SET,
	PLGRP_NO_OP
} plgrp_ops_t;

/*
 * Arguments specified to plgrp(1) and any state needed to do everything
 * that plgrp(1) does for one operation from inside Plwp_iter_all()
 */
typedef struct plgrp_args {
	struct ps_prochandle	*Ph;		/* proc handle for process */
	const char		*lwps;		/* LWPs */
	lgrp_id_t		*lgrps;		/* lgroups */
	lgrp_affinity_t		*affs;		/* lgroup affinities */
	int			nlgrps;		/* number of lgroups */
	int			nelements;	/* number of elements */
	int			index;		/* index */
	int			nthreads;	/* threads processed */
	plgrp_ops_t		op;		/* operation */
} plgrp_args_t;

/*
 * How many signals caught from terminal
 * We bail out as soon as possible when interrupt is set
 */
static int	interrupt = 0;

/*
 * How many non-fatal errors ocurred
 */
static int	nerrors = 0;

/*
 * Name of this program
 */
static char	*progname;

/*
 * Root of the lgroup hierarchy
 */
static lgrp_id_t root = LGRP_NONE;

/*
 * Bitmap of all lgroups in the system
 */
static char *lgrps_bitmap = NULL;

/*
 * Size of lgrps_bitmap array
 */
static int lgrps_bitmap_nelements = 0;

/*
 * Macro LGRP_VALID returns true when lgrp is present in the system.
 */
#define	LGRP_VALID(lgrp) (lgrps_bitmap[lgrp] != 0)


/*
 * Maximum lgroup value.
 */
static int max_lgrpid = LGRP_NONE;

/*
 * Total possible number of lgroups
 */
#define	NLGRPS (max_lgrpid + 1)


static void
usage(int rc)
{
	(void) fprintf(stderr,
	    gettext("Usage:\t%s [-h] <pid> | <core> [/lwps] ...\n"), progname);
	(void) fprintf(stderr,
	    gettext("\t%s [-F] -a <lgroup list> <pid>[/lwps] ...\n"), progname);
	(void) fprintf(stderr,
	    gettext("\t%s [-F] -A <lgroup list>/none|weak|strong[,...] "
	    " <pid>[/lwps] ...\n"), progname);
	(void) fprintf(stderr,
	    gettext("\t%s [-F] -H <lgroup list> <pid>[/lwps] ...\n"), progname);
	(void) fprintf(stderr,
	    gettext("\n\twhere <lgroup list> is a comma separated list of\n"
		"\tone or more of the following:\n\n"
		"\t  - lgroup ID\n"
		"\t  - Range of lgroup IDs specified as\n"
		"\t\t<start lgroup ID>-<end lgroup ID>\n"
		"\t  - \"all\"\n"
		"\t  - \"root\"\n"
		"\t  - \"leaves\"\n\n"));

	exit(rc);
}

/*
 * Handler for catching signals from terminal
 */
/* ARGSUSED */
static void
intr(int sig)
{
	interrupt++;
}


/*
 * Return string name for given lgroup affinity
 */
static char *
lgrp_affinity_string(lgrp_affinity_t aff)
{
	char *rc = "unknown";

	switch (aff) {
	case LGRP_AFF_STRONG:
		rc = "strong";
		break;
	case LGRP_AFF_WEAK:
		rc = "weak";
		break;
	case LGRP_AFF_NONE:
		rc = "none";
		break;
	default:
		break;
	}

	return (rc);
}


/*
 * Add a new lgroup into lgroup array in "arg", growing lgroup and affinity
 * arrays if necessary
 */
static void
lgrps_add_lgrp(plgrp_args_t *arg, int id)
{

	if (arg->nlgrps == arg->nelements) {
		arg->nelements += LGRP_BITMAP_CHUNK;

		arg->lgrps = realloc(arg->lgrps,
		    arg->nelements * sizeof (lgrp_id_t));
		if (arg->lgrps == NULL) {
			(void) fprintf(stderr, gettext("%s: out of memory\n"),
			    progname);
			exit(EXIT_FAILURE);
		}

		arg->affs = realloc(arg->affs,
		    arg->nelements * sizeof (lgrp_affinity_t));

		if (arg->affs == NULL) {
			(void) fprintf(stderr, gettext("%s: out of memory\n"),
			    progname);
			exit(EXIT_FAILURE);
		}
	}

	arg->lgrps[arg->nlgrps] = id;
	arg->affs[arg->nlgrps] = LGRP_AFF_INVALID;
	arg->nlgrps++;
}


/*
 * Return an array having '1' for each lgroup present in given subtree under
 * specified lgroup in lgroup hierarchy
 */
static void
lgrps_bitmap_init(lgrp_cookie_t cookie, lgrp_id_t lgrpid, char **bitmap_array,
	int *bitmap_nelements)
{
	lgrp_id_t	*children;
	int		i;
	int		nchildren;

	if (lgrpid < 0) {
		lgrpid = lgrp_root(cookie);
		if (lgrpid < 0)
			return;
	}

	/*
	 * If new lgroup cannot fit, grow the array and fill unused portion
	 * with zeroes.
	 */
	while (lgrpid >= *bitmap_nelements) {
		*bitmap_nelements += LGRP_BITMAP_CHUNK;
		*bitmap_array = realloc(*bitmap_array,
		    *bitmap_nelements * sizeof (char));
		if (*bitmap_array == NULL) {
			(void) fprintf(stderr, gettext("%s: out of memory\n"),
			    progname);
			exit(EXIT_FAILURE);
		}
		bzero(*bitmap_array + NLGRPS,
		    (*bitmap_nelements - NLGRPS) * sizeof (char));
	}

	/*
	 * Insert lgroup into bitmap and update max lgroup ID seen so far
	 */
	(*bitmap_array)[lgrpid] = 1;
	if (lgrpid > max_lgrpid)
		max_lgrpid = lgrpid;

	/*
	 * Get children of specified lgroup and insert descendants of each
	 * of them
	 */
	nchildren = lgrp_children(cookie, lgrpid, NULL, 0);
	if (nchildren > 0) {
		children = malloc(nchildren * sizeof (lgrp_id_t));
		if (children == NULL) {
			(void) fprintf(stderr, gettext("%s: out of memory\n"),
			    progname);
			exit(EXIT_FAILURE);
		}
		if (lgrp_children(cookie, lgrpid, children, nchildren) !=
		    nchildren) {
			free(children);
			return;
		}

		for (i = 0; i < nchildren; i++)
			lgrps_bitmap_init(cookie, children[i], bitmap_array,
			    bitmap_nelements);

		free(children);
	}
}


/*
 * Parse lgroup affinity from given string
 *
 * Return lgroup affinity or LGRP_AFF_INVALID if string doesn't match any
 * existing lgroup affinity and return pointer to position just after affinity
 * string.
 */
static lgrp_affinity_t
parse_lgrp_affinity(char *string, char  **next)
{
	int rc = LGRP_AFF_INVALID;

	if (string == NULL)
		return (LGRP_AFF_INVALID);

	/*
	 * Skip delimiter
	 */
	if (string[0] == DELIMIT_AFF)
		string++;

	/*
	 * Return lgroup affinity matching string
	 */
	if (strncmp(string, LGRP_AFF_NONE_STR, strlen(LGRP_AFF_NONE_STR))
	    == 0) {
		rc = LGRP_AFF_NONE;
		*next = string + strlen(LGRP_AFF_NONE_STR);
	} else if (strncmp(string,
			LGRP_AFF_WEAK_STR, strlen(LGRP_AFF_WEAK_STR)) == 0) {
		rc = LGRP_AFF_WEAK;
		*next = string + strlen(LGRP_AFF_WEAK_STR);
	} else if (strncmp(string, LGRP_AFF_STRONG_STR,
			strlen(LGRP_AFF_STRONG_STR)) == 0) {
		rc = LGRP_AFF_STRONG;
		*next = string + strlen(LGRP_AFF_STRONG_STR);
	}

	return (rc);
}


/*
 * Parse lgroups from given string
 * Returns the set containing all lgroups parsed or NULL.
 */
static int
parse_lgrps(lgrp_cookie_t cookie, plgrp_args_t *arg, char *s)
{
	lgrp_id_t	i;
	char		*token;

	if (cookie == LGRP_COOKIE_NONE || s == NULL || NLGRPS <= 0)
		return (0);

	/*
	 * Parse first lgroup (if any)
	 */
	token = strtok(s, DELIMIT_LGRP);
	if (token == NULL)
		return (-1);

	do {
		/*
		 * Parse lgroups
		 */
		if (isdigit(*token)) {
			lgrp_id_t	first;
			lgrp_id_t	last;
			char		*p;

			/*
			 * lgroup ID(s)
			 *
			 * Can be <lgroup ID>[-<lgroup ID>]
			 */
			p = strchr(token, DELIMIT_RANGE);
			first = atoi(token);
			if (p == NULL)
				last = first;
			else
				last = atoi(++p);

			for (i = first; i <= last; i++) {
				/*
				 * Add valid lgroups to lgroup array
				 */
				if ((i >= 0) && (i < NLGRPS) && LGRP_VALID(i))
					lgrps_add_lgrp(arg, i);
				else  {
					(void) fprintf(stderr,
					    gettext("%s: bad lgroup %d\n"),
					    progname, i);
					nerrors++;
				}
			}
		} else if (strncmp(token, LGRP_ALL_STR,
				strlen(LGRP_ALL_STR)) == 0) {
			/*
			 * Add "all" lgroups to lgroups array
			 */
			for (i = 0; i < NLGRPS; i++) {
				if (LGRP_VALID(i))
					lgrps_add_lgrp(arg, i);
			}
		} else if (strncmp(token, LGRP_ROOT_STR,
				strlen(LGRP_ROOT_STR)) == 0) {
			if (root < 0)
				root = lgrp_root(cookie);
			lgrps_add_lgrp(arg, root);
		} else if (strncmp(token, LGRP_LEAVES_STR,
		    strlen(LGRP_LEAVES_STR)) == 0) {
			/*
			 * Add leaf lgroups to lgroups array
			 */
			for (i = 0; i < NLGRPS; i++) {
				if (LGRP_VALID(i) &&
				    lgrp_children(cookie, i, NULL, 0) == 0)
					lgrps_add_lgrp(arg, i);
			}
		} else {
			return (-1);
		}
	} while (token = strtok(NULL, DELIMIT_LGRP));

	return (0);
}

/*
 * Print array of lgroup IDs, collapsing any consecutive runs of IDs into a
 * range (eg. 2,3,4 into 2-4)
 */
static void
print_lgrps(lgrp_id_t *lgrps, int nlgrps)
{
	lgrp_id_t	start;
	lgrp_id_t	end;
	int		i;

	/*
	 * Initial range consists of the first element
	 */
	start = end = lgrps[0];

	for (i = 1; i < nlgrps; i++) {
		lgrp_id_t	lgrpid;

		lgrpid = lgrps[i];
		if (lgrpid == end + 1) {
			/*
			 * Got consecutive lgroup ID, so extend end of range
			 * without printing anything since the range may extend
			 * further
			 */
			end = lgrpid;
		} else {
			/*
			 * Next lgroup ID is not consecutive, so print lgroup
			 * IDs gotten so far.
			 */
			if (end == start) {		/* same value */
				(void) printf("%d,", (int)start);
			} else if (end > start + 1) {	/* range */
				(void) printf("%d-%d,", (int)start, (int)end);
			} else {			/* different values */
				(void) printf("%d,%d,", (int)start, (int)end);
			}

			/*
			 * Try finding consecutive range starting from this
			 * lgroup ID
			 */
			start = end = lgrpid;
		}
	}

	/*
	 * Print last lgroup ID(s)
	 */
	if (end == start) {
		(void) printf("%d", (int)start);
	} else if (end > start + 1) {
		(void) printf("%d-%d", (int)start, (int)end);
	} else {
		(void) printf("%d,%d", (int)start, (int)end);
	}
}

/*
 * Print lgroup affinities given array of lgroups, corresponding array of
 * affinities, and number of elements.
 * Skip any lgroups set to LGRP_NONE or having invalid affinity.
 */
static void
print_affinities(lgrp_id_t *lgrps, lgrp_affinity_t *affs, int nelements)
{
	int		i;
	lgrp_id_t	*lgrps_none;
	lgrp_id_t	*lgrps_strong;
	lgrp_id_t	*lgrps_weak;
	int		nlgrps_none;
	int		nlgrps_strong;
	int		nlgrps_weak;

	nlgrps_strong = nlgrps_weak = nlgrps_none = 0;

	lgrps_strong = malloc(nelements * sizeof (lgrp_id_t));
	lgrps_weak = malloc(nelements * sizeof (lgrp_id_t));
	lgrps_none = malloc(nelements * sizeof (lgrp_id_t));

	if (lgrps_strong == NULL || lgrps_weak == NULL || lgrps_none == NULL) {
		(void) fprintf(stderr, gettext("%s: out of memory\n"),
		    progname);
		interrupt = 1;
		return;
	}

	/*
	 * Group lgroups by affinity
	 */
	for (i = 0; i < nelements; i++) {
		lgrp_id_t lgrpid = lgrps[i];

		/*
		 * Skip any lgroups set to LGRP_NONE
		 */
		if (lgrpid == LGRP_NONE)
			continue;

		switch (affs[i]) {
		case LGRP_AFF_STRONG:
			lgrps_strong[nlgrps_strong++] = lgrpid;
			break;
		case LGRP_AFF_WEAK:
			lgrps_weak[nlgrps_weak++] = lgrpid;
			break;
		case LGRP_AFF_NONE:
			lgrps_none[nlgrps_none++] = lgrpid;
			break;
		default:
			/*
			 * Skip any lgroups with invalid affinity.
			 */
			break;
		}
	}

	/*
	 * Print all lgroups with same affinity together
	 */
	if (nlgrps_strong) {
		print_lgrps(lgrps_strong, nlgrps_strong);
		(void) printf("/%s", lgrp_affinity_string(LGRP_AFF_STRONG));
		if (nlgrps_weak || nlgrps_none)
			(void) printf("%c", DELIMIT_AFF_LST);
	}

	if (nlgrps_weak) {
		print_lgrps(lgrps_weak, nlgrps_weak);
		(void) printf("/%s", lgrp_affinity_string(LGRP_AFF_WEAK));
		if (nlgrps_none)
			(void) printf("%c", DELIMIT_AFF_LST);
	}

	if (nlgrps_none) {
		print_lgrps(lgrps_none, nlgrps_none);
		(void) printf("/%s", lgrp_affinity_string(LGRP_AFF_NONE));
	}

	free(lgrps_strong);
	free(lgrps_weak);
	free(lgrps_none);
}


/*
 * Print heading for specified operation
 */
static void
print_heading(plgrp_ops_t op)
{

	switch (op) {
	case PLGRP_AFFINITY_GET:
		(void) printf(HDR_PLGRP_AFF_GET);
		break;

	case PLGRP_AFFINITY_SET:
		(void) printf(HDR_PLGRP_AFF_SET);
		break;

	case PLGRP_HOME_GET:
		(void) printf(HDR_PLGRP_HOME_GET);
		break;

	case PLGRP_HOME_SET:
		(void) printf(HDR_PLGRP_HOME_SET);
		break;

	default:
		break;
	}
}

/*
 * Use /proc to call lgrp_affinity_get() in another process
 */
static lgrp_affinity_t
Plgrp_affinity_get(struct ps_prochandle *Ph, idtype_t idtype, id_t id,
    lgrp_id_t lgrp)
{
	lgrp_affinity_args_t	args;
	argdes_t		Pargd[3];
	argdes_t		*Pargdp;
	int			Pnargs;
	int			Pretval;
	sysret_t		retval;
	int			syscall;

	/*
	 * Fill in arguments needed for syscall(SYS_lgrpsys,
	 * LGRP_SYS_AFFINITY_GET, 0, &args)
	 */
	syscall = SYS_lgrpsys;

	args.idtype = idtype;
	args.id = id;
	args.lgrp = lgrp;
	args.aff = LGRP_AFF_INVALID;

	/*
	 * Fill out /proc argument descriptors for syscall(SYS_lgrpsys,
	 * LGRP_SYS_AFFINITY_GET, idtype, id)
	 */
	Pnargs = LGRPSYS_NARGS;
	Pargdp = &Pargd[0];
	Pargdp->arg_value = LGRP_SYS_AFFINITY_GET;
	Pargdp->arg_object = NULL;
	Pargdp->arg_type = AT_BYVAL;
	Pargdp->arg_inout = AI_INPUT;
	Pargdp->arg_size = 0;
	Pargdp++;

	Pargdp->arg_value = 0;
	Pargdp->arg_object = NULL;
	Pargdp->arg_type = AT_BYVAL;
	Pargdp->arg_inout = AI_INPUT;
	Pargdp->arg_size = 0;
	Pargdp++;

	Pargdp->arg_value = 0;
	Pargdp->arg_object = &args;
	Pargdp->arg_type = AT_BYREF;
	Pargdp->arg_inout = AI_INPUT;
	Pargdp->arg_size = sizeof (lgrp_affinity_args_t);
	Pargdp++;

	/*
	 * Have agent LWP call syscall with appropriate arguments in target
	 * process
	 */
	Pretval = Psyscall(Ph, &retval, syscall, Pnargs, &Pargd[0]);
	if (Pretval) {
		errno = (Pretval < 0) ? ENOSYS : Pretval;
		return (LGRP_AFF_INVALID);
	}

	return (retval.sys_rval1);
}


/*
 * Use /proc to call lgrp_affinity_set() in another process
 */
static int
Plgrp_affinity_set(struct ps_prochandle *Ph, idtype_t idtype, id_t id,
    lgrp_id_t lgrp, lgrp_affinity_t aff)
{
	lgrp_affinity_args_t	args;
	argdes_t		Pargd[3];
	argdes_t		*Pargdp;
	int			Pnargs;
	int			Pretval;
	sysret_t		retval;
	int			syscall;

	/*
	 * Fill in arguments needed for syscall(SYS_lgrpsys,
	 * LGRP_SYS_AFFINITY_SET, 0, &args)
	 */
	syscall = SYS_lgrpsys;

	args.idtype = idtype;
	args.id = id;
	args.lgrp = lgrp;
	args.aff = aff;

	/*
	 * Fill out /proc argument descriptors for syscall(SYS_lgrpsys,
	 * LGRP_SYS_AFFINITY_SET, idtype, id)
	 */
	Pnargs = LGRPSYS_NARGS;
	Pargdp = &Pargd[0];
	Pargdp->arg_value = LGRP_SYS_AFFINITY_SET;
	Pargdp->arg_object = NULL;
	Pargdp->arg_type = AT_BYVAL;
	Pargdp->arg_inout = AI_INPUT;
	Pargdp->arg_size = 0;
	Pargdp++;

	Pargdp->arg_value = 0;
	Pargdp->arg_object = NULL;
	Pargdp->arg_type = AT_BYVAL;
	Pargdp->arg_inout = AI_INPUT;
	Pargdp->arg_size = 0;
	Pargdp++;

	Pargdp->arg_value = 0;
	Pargdp->arg_object = &args;
	Pargdp->arg_type = AT_BYREF;
	Pargdp->arg_inout = AI_INPUT;
	Pargdp->arg_size = sizeof (lgrp_affinity_args_t);
	Pargdp++;

	/*
	 * Have agent LWP call syscall with appropriate arguments in
	 * target process
	 */
	Pretval = Psyscall(Ph, &retval, syscall, Pnargs, &Pargd[0]);
	if (Pretval) {
		errno = (Pretval < 0) ? ENOSYS : Pretval;
		return (-1);
	}

	return (retval.sys_rval1);
}

/*
 * Use /proc to call lgrp_home() in another process
 */
static lgrp_id_t
Plgrp_home(struct ps_prochandle *Ph, idtype_t idtype, id_t id)
{
	argdes_t		Pargd[3];
	argdes_t		*Pargdp;
	int			Pnargs;
	int			Pretval;
	sysret_t		retval;
	int			syscall;

	/*
	 * Fill in arguments needed for syscall(SYS_lgrpsys,
	 * LGRP_SYS_HOME, idtype, id)
	 */
	syscall = SYS_lgrpsys;

	/*
	 * Fill out /proc argument descriptors for syscall(SYS_lgrpsys,
	 * LGRP_SYS_HOME, idtype, id)
	 */
	Pnargs = LGRPSYS_NARGS;
	Pargdp = &Pargd[0];
	Pargdp->arg_value = LGRP_SYS_HOME;
	Pargdp->arg_object = NULL;
	Pargdp->arg_type = AT_BYVAL;
	Pargdp->arg_inout = AI_INPUT;
	Pargdp->arg_size = 0;
	Pargdp++;

	Pargdp->arg_value = idtype;
	Pargdp->arg_object = NULL;
	Pargdp->arg_type = AT_BYVAL;
	Pargdp->arg_inout = AI_INPUT;
	Pargdp->arg_size = 0;
	Pargdp++;

	Pargdp->arg_value = id;
	Pargdp->arg_object = NULL;
	Pargdp->arg_type = AT_BYVAL;
	Pargdp->arg_inout = AI_INPUT;
	Pargdp->arg_size = 0;
	Pargdp++;

	/*
	 * Have agent LWP call syscall with appropriate arguments in
	 * target process
	 */
	Pretval = Psyscall(Ph, &retval, syscall, Pnargs, &Pargd[0]);
	if (Pretval) {
		errno = (Pretval < 0) ? ENOSYS : Pretval;
		return (-1);
	}

	return (retval.sys_rval1);
}

/*
 * Use /proc to call lgrp_affinity_set(3LGRP) to set home lgroup of given
 * thread
 */
static int
Plgrp_home_set(struct ps_prochandle *Ph, idtype_t idtype, id_t id,
    lgrp_id_t lgrp)
{
	return (Plgrp_affinity_set(Ph, idtype, id, lgrp,
	    LGRP_AFF_STRONG));
}


/*
 * Do plgrp(1) operation on specified thread
 */
static int
do_op(plgrp_args_t *plgrp_args, id_t pid, id_t lwpid,
    const lwpsinfo_t *lwpsinfo)
{
	lgrp_affinity_t		*affs;
	lgrp_affinity_t		*cur_affs;
	lgrp_id_t		home;
	int			i;
	lgrp_affinity_t		*init_affs;
	lgrp_id_t		*lgrps;
	lgrp_id_t		*lgrps_changed;
	int			nlgrps;
	lgrp_id_t		old_home;
	lgrp_id_t		lgrpid;
	struct ps_prochandle	*Ph;
	int			nchanged;

	/*
	 * No args, so nothing to do.
	 */
	if (plgrp_args == NULL)
		return (0);

	/*
	 * Unpack plgrp(1) arguments and state needed to process this LWP
	 */
	Ph = plgrp_args->Ph;
	lgrps = plgrp_args->lgrps;
	affs = plgrp_args->affs;
	nlgrps = plgrp_args->nlgrps;

	switch (plgrp_args->op) {

	case PLGRP_HOME_GET:
		/*
		 * Get and display home lgroup for given LWP
		 */
		home = lwpsinfo->pr_lgrp;
		(void) printf(FMT_HOME"\n", (int)home);
		break;

	case PLGRP_AFFINITY_GET:
		/*
		 * Get and display this LWP's home lgroup and affinities
		 * for specified lgroups
		 */
		home = lwpsinfo->pr_lgrp;
		(void) printf(FMT_HOME, (int)home);

		/*
		 * Collect affinity values
		 */
		for (i = 0; i < nlgrps; i++) {
			affs[i] = Plgrp_affinity_get(Ph, P_LWPID, lwpid,
			    lgrps[i]);

			if (affs[i] == LGRP_AFF_INVALID) {
				nerrors++;
				(void) fprintf(stderr,
				    gettext("%s: cannot get affinity"
					" for lgroup %d for %d/%d: %s\n"),
				    progname, lgrps[i], pid, lwpid,
				    strerror(errno));
			}
		}

		/*
		 * Print affinities for each type.
		 */
		print_affinities(lgrps, affs, nlgrps);
		(void) printf("\n");

		break;

	case PLGRP_HOME_SET:
		/*
		 * Get home lgroup before and after setting it and display
		 * change.  If more than one lgroup and one LWP are specified,
		 * then home LWPs to lgroups in round robin fashion.
		 */
		old_home = lwpsinfo->pr_lgrp;

		i = plgrp_args->index;
		if (Plgrp_home_set(Ph, P_LWPID, lwpid, lgrps[i]) != 0) {
			nerrors++;
			(void) fprintf(stderr,
			    gettext("%s: cannot set home lgroup of %d/%d"
				" to lgroup %d: %s\n"),
				progname, pid, lwpid, lgrps[i],
			    strerror(errno));
			(void) printf("\n");
		} else {
			int len;
			int width = strlen(HDR_PLGRP_HOME_CHANGE);

			home = Plgrp_home(Ph, P_LWPID, lwpid);

			if (home < 0) {
				(void) fprintf(stderr,
				    gettext("%s cannot get home lgroup for"
					" %d/%d: %s\n"),
				    progname, pid, lwpid, strerror(errno));
				nerrors++;
			}

			len = printf(FMT_NEWHOME, (int)old_home, (int)home);
			if (len < width)
				(void) printf("%*c\n", (int)(width - len), ' ');
		}

		plgrp_args->index = (i + 1) % nlgrps;

		break;

	case PLGRP_AFFINITY_SET:
		/*
		 * Set affinities for specified lgroups and print old and new
		 * affinities and any resulting change in home lgroups
		 */

		/*
		 * Get initial home lgroup as it may change.
		 */
		old_home = lwpsinfo->pr_lgrp;

		/*
		 * Need to allocate arrays indexed by lgroup (ID) for
		 * affinities and lgroups because user may specify affinity
		 * for same lgroup multiple times....
		 *
		 * Keeping these arrays by lgroup (ID) eliminates any
		 * duplication and makes it easier to just print initial and
		 * final lgroup affinities (instead of trying to keep a list
		 * of lgroups specified which may include duplicates)
		 */
		init_affs = malloc(NLGRPS * sizeof (lgrp_affinity_t));
		cur_affs = malloc(NLGRPS * sizeof (lgrp_affinity_t));
		lgrps_changed = malloc(NLGRPS * sizeof (lgrp_id_t));

		if (init_affs == NULL || cur_affs == NULL ||
		    lgrps_changed == NULL) {
			(void) fprintf(stderr, gettext("%s: out of memory\n"),
			    progname);
			Prelease(Ph, PRELEASE_RETAIN);
			if (init_affs != NULL)
				free(init_affs);
			if (cur_affs != NULL)
				free(cur_affs);
			nerrors++;
			return (EXIT_NONFATAL);
		}

		/*
		 * Initialize current and initial lgroup affinities and
		 * lgroups changed
		 */
		for (lgrpid = 0; lgrpid < NLGRPS; lgrpid++) {

			if (!LGRP_VALID(lgrpid)) {
				init_affs[lgrpid] = LGRP_AFF_INVALID;
			} else {
				init_affs[lgrpid] =
				    Plgrp_affinity_get(Ph, P_LWPID,
					lwpid, lgrpid);

				if (init_affs[lgrpid] == LGRP_AFF_INVALID) {
					nerrors++;
					(void) fprintf(stderr,
					    gettext("%s: cannot get"
						" affinity for lgroup %d"
						" for %d/%d: %s\n"),
					    progname, lgrpid, pid, lwpid,
					    strerror(errno));
				}
			}

			cur_affs[lgrpid] = init_affs[lgrpid];
			lgrps_changed[lgrpid] = LGRP_NONE;
		}

		/*
		 * Change affinities.
		 */
		for (i = 0; i < nlgrps; i++) {
			lgrp_affinity_t	aff = affs[i];

			lgrpid = lgrps[i];

			/*
			 * If the suggested affinity is the same as the current
			 * one, skip this lgroup.
			 */
			if (aff == cur_affs[lgrpid])
				continue;

			/*
			 * Set affinity to the new value
			 */
			if (Plgrp_affinity_set(Ph, P_LWPID, lwpid, lgrpid,
				aff) < 0) {
				nerrors++;
				(void) fprintf(stderr,
				    gettext("%s: cannot set"
					" %s affinity for lgroup %d"
					" for %d/%d: %s\n"),
				    progname, lgrp_affinity_string(aff),
				    lgrpid, pid, lwpid,
				    strerror(errno));
				continue;
			}

			/*
			 * Get the new value and verify that it changed as
			 * expected.
			 */
			cur_affs[lgrpid] =
			    Plgrp_affinity_get(Ph, P_LWPID, lwpid, lgrpid);

			if (cur_affs[lgrpid] == LGRP_AFF_INVALID) {
				nerrors++;
				(void) fprintf(stderr,
				    gettext("%s: cannot get"
					" affinity for lgroup %d"
					" for %d/%d: %s\n"),
				    progname, lgrpid, pid, lwpid,
				    strerror(errno));
				continue;
			}

			if (aff != cur_affs[lgrpid]) {
				(void) fprintf(stderr,
				    gettext("%s: affinity for"
					" lgroup %d is set to %d instead of %d"
					" for %d/%d\n"),
				    progname, lgrpid, cur_affs[lgrpid], aff,
				    pid, lwpid);
				nerrors++;
			}
		}

		/*
		 * Compare current and initial affinities and mark lgroups with
		 * changed affinities.
		 */
		nchanged = 0;
		for (lgrpid = 0; lgrpid < NLGRPS; lgrpid++) {
			if (init_affs[lgrpid] != cur_affs[lgrpid]) {
				lgrps_changed[lgrpid] = lgrpid;
				nchanged++;
			}
		}

		if (nchanged == 0) {
			/*
			 * Nothing changed, so just print current affinities for
			 * specified lgroups.
			 */
			for (i = 0; i < nlgrps; i++) {
				lgrps_changed[lgrps[i]] = lgrps[i];
			}

			(void) printf("%-*d",
			    (int)strlen(HDR_PLGRP_HOME_CHANGE),
			    (int)old_home);

			print_affinities(lgrps_changed, cur_affs, NLGRPS);
			(void) printf("\n");
		} else {
			int width = strlen(HDR_PLGRP_HOME_CHANGE);

			/*
			 * Some lgroup affinities changed, so display old
			 * and new home lgroups for thread and its old and new
			 * affinities for affected lgroups
			 */
			home = Plgrp_home(Ph, P_LWPID, lwpid);
			if (home < 0) {
				(void) fprintf(stderr,
				    gettext("%s: cannot get home"
					" for %d/%d: %s\n"),
				    progname, pid, lwpid, strerror(errno));
				nerrors++;
			}
			if (old_home != home) {
				int len;

				/*
				 * Fit string into fixed width
				 */
				len = printf(FMT_NEWHOME,
				    (int)old_home, (int)home);
				if (len < width)
					(void) printf("%*c", width - len, ' ');
			} else {
				(void) printf("%-*d", width, (int)home);
			}

			/*
			 * Print change in affinities from old to new
			 */
			print_affinities(lgrps_changed, init_affs, NLGRPS);
			(void) printf(" => ");
			print_affinities(lgrps_changed, cur_affs, NLGRPS);
			(void) printf("\n");
		}

		free(lgrps_changed);
		free(init_affs);
		free(cur_affs);

		break;

	default:
		break;
	}

	return (0);
}


/*
 * Routine called by Plwp_iter_all() as it iterates through LWPs of another
 * process
 */
/* ARGSUSED */
static int
Plwp_iter_handler(void *arg, const lwpstatus_t *lwpstatus,
    const lwpsinfo_t *lwpsinfo)
{
	id_t			lwpid;
	struct ps_prochandle	*Ph;
	const pstatus_t		*pstatus;
	plgrp_args_t		*plgrp_args;

	/*
	 * Nothing to do if no arguments
	 */
	if (arg == NULL || interrupt)
		return (0);

	/*
	 * Unpack plgrp(1) arguments and state needed to process this LWP
	 */
	plgrp_args = arg;
	Ph = plgrp_args->Ph;

	/*
	 * Just return if no /proc handle for process
	 */
	if (Ph == NULL)
		return (0);

	pstatus = Pstatus(Ph);

	/*
	 * Skip agent LWP and any LWPs that weren't specified
	 */
	lwpid = lwpsinfo->pr_lwpid;
	if (lwpid == pstatus->pr_agentid ||
	    !proc_lwp_in_set(plgrp_args->lwps, lwpid))
		return (0);

	plgrp_args->nthreads++;

	/*
	 * Do all plgrp(1) operations specified on given thread
	 */
	(void) printf(FMT_THREAD" ", (int)pstatus->pr_pid, (int)lwpid);
	return (do_op(plgrp_args, pstatus->pr_pid, lwpid, lwpsinfo));
}

/*
 * Get target process specified in "pidstring" argument to do operation(s)
 * specified in "plgrp_todo" using /proc and agent LWP
 */
static void
do_process(char *pidstring, plgrp_args_t *plgrp_todo, int force)
{
	int			error;
	const char		*lwps;
	struct ps_prochandle	*Ph;

	/*
	 * Nothing to do, so return.
	 */
	if (plgrp_todo == NULL || interrupt)
		return;

	/*
	 * Grab target process or core and return
	 * /proc handle for process and string of LWP
	 * IDs
	 */
	Ph = proc_arg_xgrab(pidstring, NULL,
	    PR_ARG_ANY, force | PGRAB_RETAIN | PGRAB_NOSTOP, &error, &lwps);
	if (Ph == NULL) {
		(void) fprintf(stderr,
		    gettext("%s: Unable to grab process %s: %s\n"),
		    progname, pidstring, Pgrab_error(error));
		nerrors++;
		return;
	}

	/*
	 * Fill in remaining plgrp(1) arguments and state needed to do
	 * plgrp(1) operation(s) on desired LWPs in our handler
	 * called by Plwp_iter_all() as it iterates over LWPs
	 * in given process
	 */
	plgrp_todo->Ph = Ph;
	plgrp_todo->lwps = lwps;

	/*
	 * Iterate over LWPs in process and do specified
	 * operation(s) on those specified
	 */
	if (Plwp_iter_all(Ph, Plwp_iter_handler, plgrp_todo) != 0) {
		(void) fprintf(stderr,
		    gettext("%s: error iterating over threads\n"),
		    progname);
		nerrors++;
	}

	Prelease(Ph, PRELEASE_RETAIN);
}


/*
 * Parse command line and kick off any resulting actions
 *
 * plgrp(1) has the following command line syntax:
 *
 *	plgrp [-h] <pid> | <core> [/lwps] ...
 *	plgrp [-F] -a <lgroup>,... <pid>[/lwps] ...
 *	plgrp [-F] -H <lgroup>,... <pid>[/lwps] ...
 *	plgrp [-F] -A <lgroup>,... [/none|weak|strong] ... <pid>[/lwps] ...
 *
 *	where <lgroup> is an lgroup ID, "all", "root", "leaves".
 */
int
main(int argc, char *argv[])
{
	lgrp_affinity_t		aff;
	char			*affstring;
	int			c;
	lgrp_cookie_t		cookie;
	int			Fflag;
	int			i;
	int			opt_seen;
	plgrp_args_t		plgrp_todo;
	char			*s;

	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	opt_seen = 0;

	/*
	 * Get name of program
	 */
	progname = basename(argv[0]);

	/*
	 * Not much to do when only name of program given
	 */
	if (argc == 1)
		usage(0);

	/*
	 * Catch signals from terminal, so they can be handled asynchronously
	 * when we're ready instead of when we're not (;-)
	 */
	if (sigset(SIGHUP, SIG_IGN) == SIG_DFL)
		(void) sigset(SIGHUP, intr);
	if (sigset(SIGINT, SIG_IGN) == SIG_DFL)
		(void) sigset(SIGINT, intr);
	if (sigset(SIGQUIT, SIG_IGN) == SIG_DFL)
		(void) sigset(SIGQUIT, intr);
	(void) sigset(SIGPIPE, intr);
	(void) sigset(SIGTERM, intr);

	/*
	 * Take snapshot of lgroup hierarchy
	 */
	cookie = lgrp_init(LGRP_VIEW_OS);
	if (cookie == LGRP_COOKIE_NONE) {
		(void) fprintf(stderr,
		    gettext("%s: Fatal error: cannot get lgroup"
			" information from the OS: %s\n"),
		    progname, strerror(errno));
		return (EXIT_FAILURE);
	}

	root = lgrp_root(cookie);
	lgrps_bitmap_init(cookie, root, &lgrps_bitmap, &lgrps_bitmap_nelements);

	/*
	 * Remember arguments and state needed to do plgrp(1) operation
	 * on desired LWPs
	 */
	bzero(&plgrp_todo, sizeof (plgrp_args_t));
	plgrp_todo.op = PLGRP_HOME_GET;

	/*
	 * Parse options
	 */
	opterr = 0;
	Fflag = 0;
	while (!interrupt && (c = getopt(argc, argv, "a:A:FhH:")) != -1) {
		/*
		 * Parse option and only allow one option besides -F to be
		 * specified
		 */
		switch (c) {

		case 'h':	/* Get home lgroup */
			/*
			 * Only allow one option (besides -F) to be specified
			 */
			if (opt_seen)
				usage(EXIT_FAILURE);
			opt_seen = 1;

			plgrp_todo.op = PLGRP_HOME_GET;
			break;

		case 'H':	/* Set home lgroup */

			/*
			 * Fail if already specified option (besides -F)
			 * or no more arguments
			 */
			if (opt_seen || optind >= argc) {
				usage(EXIT_FAILURE);
			}
			opt_seen = 1;

			plgrp_todo.op = PLGRP_HOME_SET;

			if (parse_lgrps(cookie, &plgrp_todo, optarg) < 0)
				usage(EXIT_FAILURE);

			/* If there are no valid lgroups exit immediately */
			if (plgrp_todo.nlgrps == 0) {
				(void) fprintf(stderr,
				    gettext("%s: no valid lgroups"
					" specified for -%c\n\n"),
				    progname, c);
				    usage(EXIT_FAILURE);
			}

			break;

		case 'a':	/* Get lgroup affinity */

			/*
			 * Fail if already specified option (besides -F)
			 * or no more arguments
			 */
			if (opt_seen || optind >= argc) {
				usage(EXIT_FAILURE);
			}
			opt_seen = 1;

			plgrp_todo.op = PLGRP_AFFINITY_GET;

			if (parse_lgrps(cookie, &plgrp_todo, optarg) < 0)
				usage(EXIT_FAILURE);

			/* If there are no valid lgroups exit immediately */
			if (plgrp_todo.nlgrps == 0) {
				(void) fprintf(stderr,
				    gettext("%s: no valid lgroups specified"
					" for -%c\n\n"),
				    progname, c);
				    usage(EXIT_FAILURE);
			}

			break;

		case 'A':	/* Set lgroup affinity */

			/*
			 * Fail if already specified option (besides -F)
			 * or no more arguments
			 */
			if (opt_seen || optind >= argc) {
				usage(EXIT_FAILURE);
			}
			opt_seen = 1;

			plgrp_todo.op = PLGRP_AFFINITY_SET;

			/*
			 * 'affstring' is the unparsed prtion of the affinity
			 * specification like 1,2/none,2/weak,0/strong
			 *
			 * 'next' is the next affinity specification to parse.
			 */
			affstring = optarg;
			while (affstring != NULL && strlen(affstring) > 0) {
				char	*next;

				/*
				 * affstring points to the first affinity
				 * specification. Split the string by
				 * DELIMIT_AFF separator and parse lgroups and
				 * affinity value separately.
				 */
				s = strchr(affstring, DELIMIT_AFF);
				if (s == NULL) {
					(void) fprintf(stderr,
					    gettext("%s: invalid "
						"syntax >%s<\n"),
					    progname, affstring);
					usage(EXIT_FAILURE);
				}

				aff = parse_lgrp_affinity(s, &next);
				if (aff == LGRP_AFF_INVALID) {
					(void) fprintf(stderr,
					    gettext("%s: invalid "
						"affinity >%s<\n"),
					    progname, affstring);
					usage(EXIT_FAILURE);
				}

				/*
				 * next should either point to the empty string
				 * or to the DELIMIT_AFF_LST separator.
				 */
				if (*next != '\0') {
					if (*next != DELIMIT_AFF_LST) {
						(void) fprintf(stderr,
						    gettext("%s: invalid "
							"syntax >%s<\n"),
						    progname, next);
						usage(EXIT_FAILURE);
					}
					*next = '\0';
					next++;
				}


				/*
				 * Now parse the list of lgroups
				 */
				if (parse_lgrps(cookie, &plgrp_todo,
					affstring) < 0) {
					usage(EXIT_FAILURE);
				}

				/*
				 * Set desired affinity for specified lgroup to
				 * the specified affinity.
				 */
				for (i = 0; i < plgrp_todo.nlgrps; i++) {
					if (plgrp_todo.affs[i] ==
					    LGRP_AFF_INVALID)
						plgrp_todo.affs[i] = aff;
				}

				/*
				 * We processed the leftmost element of the
				 * list. Advance affstr to the remaining part of
				 * the list. and repeat.
				 */
				affstring = next;
			}

			/*
			 * If there are no valid lgroups, exit immediately
			 */
			if (plgrp_todo.nlgrps == 0) {
				(void) fprintf(stderr,
				    gettext("%s: no valid lgroups specified "
				    "for -%c\n\n"), progname, c);
				    usage(EXIT_FAILURE);
			}

			break;

		case 'F':	/* Force */

			/*
			 * Only allow one occurrence
			 */
			if (Fflag != 0) {
				usage(EXIT_FAILURE);
			}

			/*
			 * Set flag to force /proc to grab process even though
			 * it's been grabbed by another process already
			 */
			Fflag = PGRAB_FORCE;
			break;

		case '?':	/* Unrecognized option */
		default:
			usage(EXIT_FAILURE);
			break;

		}
	}

	/*
	 * Should have more arguments left at least for PID or core
	 */
	if (optind >= argc)
		usage(EXIT_FAILURE);

	(void) lgrp_fini(cookie);

	/*
	 * Print heading and process each [pid | core]/lwps argument
	 */
	print_heading(plgrp_todo.op);
	(void) proc_initstdio();

	for (i = optind; i < argc && !interrupt; i++) {
		(void) proc_flushstdio();
		do_process(argv[i], &plgrp_todo, Fflag);
	}

	(void) proc_finistdio();

	if (plgrp_todo.nthreads == 0) {
		(void) fprintf(stderr, gettext("%s: no matching LWPs found\n"),
		    progname);
	}

	return ((nerrors ||interrupt) ? EXIT_NONFATAL : EXIT_SUCCESS);
}