/*
* 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
* 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 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2013, Joyent, Inc. All rights reserved.
*/
/*
* pargs examines and prints the arguments (argv), environment (environ),
* and auxiliary vector of another process.
*
* This utility is made more complex because it must run in internationalized
* environments. The two key cases for pargs to manage are:
*
* 1. pargs and target run in the same locale: pargs must respect the
* locale, but this case is straightforward. Care is taken to correctly
* use wide characters in order to print results properly.
*
* 2. pargs and target run in different locales: in this case, pargs examines
* the string having assumed the victim's locale. Unprintable (but valid)
* characters are escaped. Next, iconv(3c) is used to convert between the
* target and pargs codeset. Finally, a second pass to escape unprintable
* (but valid) characters is made.
*
* In any case in which characters are encountered which are not valid in
* their purported locale, the string "fails" and is treated as a traditional
* 7-bit ASCII encoded string, and escaped accordingly.
*/
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>
#include <iconv.h>
#include <langinfo.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <limits.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <setjmp.h>
#include <sys/archsystm.h>
#include <libproc.h>
#include <wctype.h>
#include <widec.h>
#include <elfcap.h>
typedef struct pargs_data {
char **pd_argv_strs;
char **pd_envp_strs;
char **pd_auxv_strs;
char *pd_execname;
} pargs_data_t;
static char *command;
static int dmodel;
static void *
{
void *p;
/*
* If the malloc fails we longjmp out to allow the code to Prelease()
* a stopped victim if needed.
*/
}
return (p);
}
static char *
{
char *s2;
return (s2);
}
/*
* Given a wchar_t which might represent an 'escapable' sequence (see
* formats(5)), return the base ascii character needed to print that
* sequence.
*
* The comparisons performed may look suspect at first, but all are valid;
* the characters below all appear in the "Portable Character Set." The
* Single Unix Spec says: "The wide-character value for each member of the
* Portable Character Set will equal its value when used as the lone
* character in an integer character constant."
*/
static uchar_t
{
switch (wc) {
case L'\a':
return ('a');
case L'\b':
return ('b');
case L'\f':
return ('f');
case L'\n':
return ('n');
case L'\r':
return ('r');
case L'\t':
return ('t');
case L'\v':
return ('v');
case L'\\':
return ('\\');
}
return ('\0');
}
static char *
{
while ((c = *src++) != '\0') {
/*
* Call get_interp_char *first*, since \ will otherwise not
* be escaped as \\.
*/
*ucp++ = '\\';
*ucp++ = c;
} else {
*ucp++ = '\\';
*unprintable = 1;
}
}
*ucp = '\0';
return ((char *)uc);
}
/*
* Convert control characters as described in format(5) to their readable
* representation; special care is taken to handle multibyte character sets.
*
* If escape_slash is true, escaping of '\' occurs. The first time a string
* is unctrl'd, this should be '1'. Subsequent iterations over the same
* string should set escape_slash to 0. Otherwise you'll wind up with
* \ --> \\ --> \\\\.
*/
static char *
{
char *uc;
/*
* We can't trust the string, since in the locale in which
* this call is operating, the string contains an invalid
* multibyte sequence. There isn't much to do here, so
* convert the string byte by byte to wide characters, as
* if it came from a C locale (char) string. This isn't
* perfect, but at least the characters will make it to
* the screen.
*/
unprintable));
}
}
int len, i;
char c = get_interp_char(wc);
/*
* Print "interpreted version" (\n, \a, etc).
*/
*wide_destp++ = L'\\';
*wide_destp++ = (wchar_t)c;
continue;
}
*wide_destp++ = wc;
continue;
}
/*
* Convert the wide char back into (potentially several)
* multibyte characters, then escape out each of those bytes.
*/
/*
* This is a totally invalid wide char; discard it.
*/
continue;
}
for (i = 0; i < len; i++) {
*wide_destp++ = L'\\';
*unprintable = 1;
}
}
*wide_destp = '\0';
/* If we've gotten this far, wcstombs shouldn't fail... */
exit(1);
} else {
char *tmp;
/*
* Try to save memory; don't waste 3 * strlen in the
* common case.
*/
}
return (uc);
}
/*
* These functions determine which characters are safe to be left unquoted.
* Rather than starting with every printable character and subtracting out the
* shell metacharacters, we take the more conservative approach of starting with
* a set of safe characters and adding those few common punctuation characters
* which are known to be safe. The rules are:
*
* If this is a printable character (graph), and not punctuation, it is
* safe to leave unquoted.
*
* If it's one of known hard-coded safe characters, it's also safe to leave
* unquoted.
*
* Otherwise, the entire argument must be quoted.
*
* This will cause some strings to be unecessarily quoted, but it is safer than
* having a character unintentionally interpreted by the shell.
*/
static int
issafe_ascii(char c)
{
}
static int
{
}
/*ARGSUSED*/
static char *
{
char *dst;
int quote_count = 0;
int need_quote = 0;
if (!issafe_ascii(*srcp)) {
need_quote = 1;
if (*srcp == '\'')
quote_count++;
}
}
if (!need_quote)
return (src);
/*
* The only character we care about here is a single quote. All the
* other unprintable characters (and backslashes) will have been dealt
* with by unctrl_str(). We make the following subtitution when we
* encounter a single quote:
*
* ' = '"'"'
*
* In addition, we put single quotes around the entire argument. For
* example:
*
* foo'bar = 'foo'"'"'bar'
*/
*dstp++ = '\'';
if (*srcp == '\'') {
dstp += 4;
}
}
*dstp++ = '\'';
*dstp = '\0';
return (dst);
}
static char *
{
char *uc;
int quote_count = 0;
int need_quote = 0;
}
need_quote = 1;
if (*wide_srcp == L'\'')
quote_count++;
}
}
if (!need_quote) {
return (src);
}
/*
* See comment for quote_string_ascii(), above.
*/
*wide_destp++ = L'\'';
wide_srcp++, wide_destp++) {
*wide_destp = *wide_srcp;
if (*wide_srcp == L'\'') {
wide_destp += 4;
}
}
*wide_destp++ = L'\'';
*wide_destp = L'\0';
/* If we've gotten this far, wcstombs shouldn't fail... */
exit(1);
}
return (uc);
}
/*
* Determine the locale of the target process by traversing its environment,
* making only one pass for efficiency's sake; stash the result in
* datap->pd_locale.
*
* It's possible that the process has called setlocale() to change its
* locale to something different, but we mostly care about making a good
* guess as to the locale at exec(2) time.
*/
static void
{
int i, j, composite = 0;
char *pd_locale;
static const char *cat_names[] = {
"LC_CTYPE=", "LC_NUMERIC=", "LC_TIME=",
"LC_COLLATE=", "LC_MONETARY=", "LC_MESSAGES="
};
char *s = datap->pd_envp_strs[i];
if (s == NULL)
continue;
/*
* Minor optimization-- if we find LC_ALL we're done.
*/
break;
}
for (j = 0; j <= _LastCategory; j++) {
}
}
}
}
for (i = 0; i <= _LastCategory; i++) {
} else {
lcs[i] = "C";
}
composite++;
}
if (composite == 0) {
/* simple locale */
} else {
/* composite locale */
}
}
/*
* Pull a string from the victim, regardless of size; this routine allocates
* memory for the string which must be freed by the caller.
*/
static char *
{
char *result;
for (;;) {
return (NULL);
size *= 2;
} else {
break;
}
}
return (result);
}
/*
* Utility function to read an array of pointers from the victim, adjusting
* for victim data model; returns the number of bytes successfully read.
*/
static ssize_t
{
if (dmodel == PR_MODEL_NATIVE) {
offset);
} else {
int i;
offset);
if (res > 0) {
for (i = 0; i < nelems; i++)
}
}
return (res);
}
/*
* Extract the argv array from the victim; store the pointer values in
* datap->pd_argv and the extracted strings in datap->pd_argv_strs.
*/
static void
{
int i;
return;
}
for (i = 0; i < argc; i++) {
continue;
}
}
/*ARGSUSED*/
static int
{
/* env has more items than last time, skip the newer ones */
return (0);
else
}
return (0);
}
static void
{
}
/*
* The following at_* routines are used to decode data from the aux vector.
*/
/*ARGSUSED*/
static void
{
str[0] = '\0';
}
/*ARGSUSED*/
static void
{
str[0] = '\0';
}
}
/*
* Note: Don't forget to add a corresponding case to isainfo(1).
*/
/*ARGSUSED*/
static void
{
#else
#error "port me"
#endif
}
/*ARGSUSED*/
static void
{
#else
#error "port me"
#endif
}
/*ARGSUSED*/
static void
{
str[0] = '\0';
else
}
/*ARGSUSED*/
static void
{
str[0] = '\0';
else
}
static struct auxfl {
int af_flag;
const char *af_name;
} auxfl[] = {
{ AF_SUN_SETUGID, "setugid" },
};
/*ARGSUSED*/
static void
{
int i;
*str = '\0';
if (*str != '\0')
}
}
}
struct aux_id {
int aux_type;
const char *aux_name;
};
};
/*
* Return the aux_id entry for the given aux type; returns NULL if not found.
*/
static struct aux_id *
{
int i;
for (i = 0; i < N_AT_ENTS; i++) {
return (&aux_arr[i]);
}
return (NULL);
}
static void
{
int i;
/*
* Fetch the aux vector from the target process.
*/
return;
continue;
/*
* Grab strings for those entries which have a string-decoder.
*/
datap->pd_auxv_strs[i] =
}
}
}
/*
* Prepare to convert characters in the victim's character set into user's
* character set.
*/
static void
{
mylocale = "C";
goto done;
/*
* If the target's locale is "C" or "POSIX", go fast.
*/
goto done;
}
/*
* Switch to the victim's locale, and discover its character set.
*/
"%s: Couldn't determine locale of target process.\n",
command);
"%s: Some strings may not be displayed properly.\n",
command);
goto done;
}
/*
* Get LC_CTYPE part of target's locale, and its codeset.
*/
/*
* Now go fully back to the pargs user's locale.
*/
/*
* It's safe to bail here if the lc_ctype of the locales are the
* same-- we know that their encodings and characters sets are the same.
*/
goto done;
*diflocale = 1;
/*
* If the codeset of the victim matches our codeset then iconv need
* not be involved.
*/
goto done;
== (iconv_t)-1) {
/*
* EINVAL indicates there was no conversion available
* from victim charset to mycharset
*/
"%s: failed to initialize iconv: %s\n",
exit(1);
}
} else {
}
done:
}
static void
{
}
}
static char *
{
const char *instrptr;
for (;;) {
/*
* Generate the "initial shift state" sequence, placing that
* at the head of the string.
*/
inleft = 0;
/*
* Outstr must be null terminated upon exit from
* iconv().
*/
break;
bufsz *= 2;
return (NULL);
} else {
/*
* iconv() could in theory return EBADF, but that
* shouldn't happen.
*/
"%s: iconv(3C) failed unexpectedly: %s\n",
exit(1);
}
}
return (outstr);
}
/*
* Returns a freshly allocated string converted to the local character set,
* removed of unprintable characters.
*/
static char *
{
return (retstr);
}
/*
* If we aren't using iconv(), convert control chars in
* the string in pargs' locale, since that is the display
* locale.
*/
return (retstr);
}
/*
* The logic here is a bit (ahem) tricky. Start by converting
* unprintable characters *in the target's locale*. This should
* eliminate a variety of unprintable or illegal characters-- in
* short, it should leave us with something which iconv() won't
* have trouble with.
*
* After allowing iconv to convert characters as needed, run unctrl
* again in pargs' locale-- This time to make sure that any
* characters which aren't printable according to the *current*
* locale (independent of the current codeset) get taken care of.
* Without this second stage, we might (for example) fail to
* properly handle characters converted into the 646 character set
* (which are 8-bits wide), but which must be displayed in the C
* locale (which uses 646, but whose printable characters are a
* subset of the 7-bit characters).
*
* Note that assuming the victim's locale using LC_ALL will be
* problematic when pargs' messages are internationalized in the
* future (and it calls textdomain(3C)). In this case, any
* error message fprintf'd in unctrl_str() will be in the wrong
* LC_MESSAGES class. We'll cross that bridge when we come to it.
*/
/*
* In this (rare but real) case, the iconv() failed even
* though we unctrl'd the string. Treat the original string
* (str) as a C locale string and strip it that way.
*/
}
/*
* Run unctrl_str, but make sure not to escape \ characters, which
* may have resulted from the first round of unctrl.
*/
return (retstr);
}
static void
{
int i;
char *tmp;
return;
for (i = 0; i < count; i++) {
continue;
}
}
/*
* Free data allocated during the gathering phase.
*/
static void
{
int i;
}
}
}
}
}
}
}
static void
{
int i;
return;
}
(void) printf("argv[%d]: ", i);
(void) printf("<NULL>\n");
(void) printf("<0x%0*lx>\n",
} else {
}
}
}
static void
{
int i;
return;
}
(void) printf("envp[%d]: ", i);
break;
(void) printf("<0x%0*lx>\n",
} else {
}
}
}
static int
{
int i;
/*
* Go through and check to see if we have valid data. If not, print
* an error message and bail.
*/
"argument list\n", command);
return (1);
}
datap->pd_argv_strs[i] =
}
"executable\n", command);
return (1);
}
(void) printf("\n");
return (0);
}
static void
{
int i;
/*
* Print the names and values of all the aux vector entries.
*/
long v;
/*
* Fetch aux vector type string and decoded
* representation of the value.
*/
} else {
decode[0] = '\0';
}
}
}
int
{
int opt;
command++;
else
switch (opt) {
case 'a': /* show process arguments */
aflag++;
break;
case 'c': /* force 7-bit ascii */
cflag++;
break;
case 'e': /* show environment variables */
eflag++;
break;
case 'l':
lflag++;
aflag++; /* -l implies -a */
break;
case 'x': /* show aux vector entries */
xflag++;
break;
case 'F':
/*
* Since we open the process read-only, there is no need
* for the -F flag. It's a documented flag, so we
* consume it silently.
*/
break;
default:
errflg++;
break;
}
}
/* -a is the default if no options are specified */
aflag++;
}
/* -l cannot be used with the -x or -e flags */
errflg++;
}
"usage: %s [-aceFlx] { pid | core } ...\n"
" (show process arguments and environment)\n"
" -a: show process arguments (default)\n"
" -c: interpret characters as 7-bit ascii regardless of "
"locale\n"
" -e: show environment variables\n"
" -F: force grabbing of the target process\n"
" -l: display arguments as command line\n"
" -x: show aux vector entries\n", command);
return (2);
}
while (argc-- > 0) {
char *arg;
int gret, r;
char *psargs_conv;
char *info;
int pstate;
int unprintable;
int diflocale;
/*
* Suppress extra blanks lines if we've encountered processes
* which can't be opened.
*/
if (error == 0) {
(void) printf("\n");
}
error = 0;
/*
* First grab just the psinfo information, in case this
* process is a zombie (in which case proc_arg_grab() will
* fail). If so, print a nice message and continue.
*/
&gret) == -1) {
retc++;
error = 1;
continue;
}
continue;
}
/*
* If process is a "system" process (like pageout), just
* print its psargs and continue on.
*/
if (!lflag)
continue;
}
/*
* Open the process readonly, since we do not need to write to
* the control file.
*/
retc++;
error = 1;
continue;
}
retc++;
continue;
}
/*
* If malloc() fails, we return here so that we can let go
* of the victim, restore our locale, print a message,
* then exit.
*/
return (1);
}
if (cflag)
/*
* Strip control characters, then record process summary in
* a buffer, since we don't want to print anything out until
* after we release the process.
*/
/*
* The process is neither a system process nor defunct.
*
* Do printing and post-processing (like name lookups) after
* gathering the raw data from the process and releasing it.
* This way, we don't deadlock on (for example) name lookup
* if we grabbed the nscd and do 'pargs -x'.
*
* We always fetch the environment of the target, so that we
* can make an educated guess about its locale.
*/
if (aflag != 0)
if (xflag != 0)
/*
* If malloc() fails after this poiint, we return here to
* restore our locale and print a message. If we don't
* reset this, we might erroneously try to Prelease a process
* twice.
*/
return (1);
}
/*
* For the -l option, we need a proper name for this executable
* before we release it.
*/
if (lflag)
sizeof (execname));
/*
* Crawl through the environment to determine the locale of
* the target.
*/
diflocale = 0;
if (lflag != 0) {
unprintable = 0;
if (diflocale)
"locale differs from current locale\n",
command);
else if (unprintable)
"line contains unprintable characters\n",
command);
} else {
&unprintable);
"core '%s' of %d:\t%s\n",
} else {
}
if (aflag != 0) {
print_args(&datap);
(void) printf("\n");
}
if (eflag != 0) {
if (xflag)
(void) printf("\n");
}
if (xflag != 0) {
print_auxv(&datap);
}
}
}
return (retc != 0 ? 1 : 0);
}