92071e963e
pmap would previously print the process name if /proc/PID/smaps could be opened, even if subsequent reads failed. This actually occurs with other users PIDs. Kernel 3.18rc1 introduced a change where the file could not been opened, meaning pmap -X 1 previously showed the process name and nothing else but NOW shows nothing make check failed because of this. This change prints the process name even before trying to open the file, returning it to previous behaviour. Thanks to Vincent Bernat for some analysis. References: https://bugs.debian.org/775624 https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/commit/?id=29a40ace841cba9b661711f042d1821cdc4ad47c Signed-off-by: Craig Small <csmall@enc.com.au>
1190 lines
31 KiB
C
1190 lines
31 KiB
C
/*
|
|
* pmap.c - print process memory mapping
|
|
* Copyright 2002 Albert Cahalan
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <getopt.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ipc.h>
|
|
#include <sys/shm.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
|
|
#include "c.h"
|
|
#include "fileutils.h"
|
|
#include "nls.h"
|
|
#include "proc/escape.h"
|
|
#include "xalloc.h"
|
|
#include "proc/readproc.h"
|
|
#include "proc/version.h"
|
|
|
|
const char *nls_Address,
|
|
*nls_Offset,
|
|
*nls_Device,
|
|
*nls_Mapping,
|
|
*nls_Perm,
|
|
*nls_Inode,
|
|
*nls_Kbytes,
|
|
*nls_Mode,
|
|
*nls_RSS,
|
|
*nls_Dirty;
|
|
|
|
static void nls_initialize(void)
|
|
{
|
|
setlocale (LC_ALL, "");
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
textdomain(PACKAGE);
|
|
|
|
/* these are the headings shared across all options,
|
|
though their widths when output might differ */
|
|
nls_Address = _("Address");
|
|
nls_Offset = _("Offset");
|
|
nls_Device = _("Device");
|
|
nls_Mapping = _("Mapping");
|
|
|
|
/* these headings are used only by the -X/-XX options,
|
|
and are not derived literally from /proc/#/smaps */
|
|
nls_Perm = _("Perm");
|
|
nls_Inode = _("Inode");
|
|
|
|
/* these are potentially used for options other than -X/-XX, */
|
|
nls_Kbytes = _("Kbytes");
|
|
nls_Mode = _("Mode");
|
|
nls_RSS = _("RSS");
|
|
nls_Dirty = _("Dirty");
|
|
}
|
|
|
|
static int justify_print(const char *str, int width, int right)
|
|
{
|
|
if (width < 1)
|
|
puts(str);
|
|
else {
|
|
int len = strlen(str);
|
|
if (width < len) width = len;
|
|
printf(right ? "%*.*s " : "%-*.*s ", width, width, str);
|
|
}
|
|
return width;
|
|
}
|
|
|
|
static int integer_width(unsigned KLONG number)
|
|
{
|
|
int result;
|
|
|
|
result = !(number > 0);
|
|
while (number) {
|
|
result++;
|
|
number /= 10;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
static void __attribute__ ((__noreturn__))
|
|
usage(FILE * out)
|
|
{
|
|
fputs(USAGE_HEADER, out);
|
|
fprintf(out,
|
|
_(" %s [options] PID [PID ...]\n"), program_invocation_short_name);
|
|
fputs(USAGE_OPTIONS, out);
|
|
fputs(_(" -x, --extended show details\n"), out);
|
|
fputs(_(" -X show even more details\n"), out);
|
|
fputs(_(" WARNING: format changes according to /proc/PID/smaps\n"), out);
|
|
fputs(_(" -XX show everything the kernel provides\n"), out);
|
|
fputs(_(" -c, --read-rc read the default rc\n"), out);
|
|
fputs(_(" -C, --read-rc-from=<file> read the rc from file\n"), out);
|
|
fputs(_(" -n, --create-rc create new default rc\n"), out);
|
|
fputs(_(" -N, --create-rc-to=<file> create new rc to file\n"), out);
|
|
fputs(_(" NOTE: pid arguments are not allowed with -n, -N\n"), out);
|
|
fputs(_(" -d, --device show the device format\n"), out);
|
|
fputs(_(" -q, --quiet do not display header and footer\n"), out);
|
|
fputs(_(" -p, --show-path show path in the mapping\n"), out);
|
|
fputs(_(" -A, --range=<low>[,<high>] limit results to the given range\n"), out);
|
|
fputs(USAGE_SEPARATOR, out);
|
|
fputs(USAGE_HELP, out);
|
|
fputs(USAGE_VERSION, out);
|
|
fprintf(out, USAGE_MAN_TAIL("pmap(1)"));
|
|
exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
|
|
}
|
|
|
|
static char mapbuf[1024];
|
|
static char cmdbuf[512];
|
|
|
|
static unsigned KLONG range_low;
|
|
static unsigned KLONG range_high = ~0ull;
|
|
|
|
static int c_option;
|
|
static int C_option;
|
|
static int d_option;
|
|
static int n_option;
|
|
static int N_option;
|
|
static int q_option;
|
|
static int x_option;
|
|
static int X_option;
|
|
|
|
static int map_desc_showpath;
|
|
|
|
static unsigned shm_minor = ~0u;
|
|
|
|
static void discover_shm_minor(void)
|
|
{
|
|
void *addr;
|
|
int shmid;
|
|
char mapbuf_b[256];
|
|
|
|
if (!freopen("/proc/self/maps", "r", stdin))
|
|
return;
|
|
|
|
/* create */
|
|
shmid = shmget(IPC_PRIVATE, 42, IPC_CREAT | 0666);
|
|
if (shmid == -1)
|
|
/* failed; oh well */
|
|
return;
|
|
/* attach */
|
|
addr = shmat(shmid, NULL, SHM_RDONLY);
|
|
if (addr == (void *)-1)
|
|
goto out_destroy;
|
|
|
|
while (fgets(mapbuf_b, sizeof mapbuf_b, stdin)) {
|
|
char perms[32];
|
|
/* to clean up unprintables */
|
|
char *tmp;
|
|
unsigned KLONG start, end;
|
|
unsigned long long file_offset, inode;
|
|
unsigned dev_major, dev_minor;
|
|
sscanf(mapbuf_b, "%" KLF "x-%" KLF "x %31s %llx %x:%x %llu", &start,
|
|
&end, perms, &file_offset, &dev_major, &dev_minor,
|
|
&inode);
|
|
tmp = strchr(mapbuf_b, '\n');
|
|
if (tmp)
|
|
*tmp = '\0';
|
|
tmp = mapbuf_b;
|
|
while (*tmp) {
|
|
if (!isprint(*tmp))
|
|
*tmp = '?';
|
|
tmp++;
|
|
}
|
|
if (start > (unsigned long)addr)
|
|
continue;
|
|
if (dev_major)
|
|
continue;
|
|
if (perms[3] != 's')
|
|
continue;
|
|
if (strstr(mapbuf_b, "/SYSV")) {
|
|
shm_minor = dev_minor;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (shmdt(addr))
|
|
perror(_("shared memory detach"));
|
|
|
|
out_destroy:
|
|
if (shmctl(shmid, IPC_RMID, NULL))
|
|
perror(_("shared memory remove"));
|
|
|
|
return;
|
|
}
|
|
|
|
static char *mapping_name(proc_t * p, unsigned KLONG addr,
|
|
unsigned KLONG len, const char *mapbuf_b,
|
|
unsigned showpath, unsigned dev_major,
|
|
unsigned dev_minor, unsigned long long inode)
|
|
{
|
|
char *cp;
|
|
|
|
if (!dev_major && dev_minor == shm_minor && strstr(mapbuf_b, "/SYSV")) {
|
|
static char shmbuf[64];
|
|
snprintf(shmbuf, sizeof shmbuf, " [ shmid=0x%llx ]", inode);
|
|
return shmbuf;
|
|
}
|
|
|
|
cp = strrchr(mapbuf_b, '/');
|
|
if (cp) {
|
|
if (showpath)
|
|
return strchr(mapbuf_b, '/');
|
|
return cp[1] ? cp + 1 : cp;
|
|
}
|
|
|
|
cp = strchr(mapbuf_b, '/');
|
|
if (cp) {
|
|
if (showpath)
|
|
return cp;
|
|
/* it WILL succeed */
|
|
return strrchr(cp, '/') + 1;
|
|
}
|
|
|
|
cp = _(" [ anon ]");
|
|
if ((p->start_stack >= addr) && (p->start_stack <= addr + len))
|
|
cp = _(" [ stack ]");
|
|
return cp;
|
|
}
|
|
|
|
|
|
#define DETAIL_LENGTH 32
|
|
#define DETL "31" /* for format strings */
|
|
#define NUM_LENGTH 21 /* python says: len(str(2**64)) == 20 */
|
|
#define NUML "20" /* for format strings */
|
|
#define VMFLAGS_LENGTH 81 /* There are 27 posible 2 character vmflags as of this patch */
|
|
|
|
struct listnode {
|
|
char description[DETAIL_LENGTH];
|
|
char value_str[NUM_LENGTH];
|
|
unsigned KLONG value;
|
|
unsigned KLONG total;
|
|
int max_width;
|
|
struct listnode *next;
|
|
};
|
|
|
|
static struct listnode *listhead=NULL, *listtail=NULL, *listnode;
|
|
|
|
|
|
struct cnf_listnode {
|
|
char description[DETAIL_LENGTH];
|
|
struct cnf_listnode *next;
|
|
};
|
|
|
|
static struct cnf_listnode *cnf_listhead=NULL, *cnf_listnode;
|
|
|
|
static int is_unimportant (const char *s)
|
|
{
|
|
if (strcmp(s, "AnonHugePages") == 0) return 1;
|
|
if (strcmp(s, "KernelPageSize") == 0) return 1;
|
|
if (strcmp(s, "MMUPageSize") == 0) return 1;
|
|
if (strcmp(s, "Shared_Dirty") == 0) return 1;
|
|
if (strcmp(s, "Private_Dirty") == 0) return 1;
|
|
if (strcmp(s, "Shared_Clean") == 0) return 1;
|
|
if (strcmp(s, "Private_Clean") == 0) return 1;
|
|
if (strcmp(s, "VmFlags") == 0) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* check, whether we want to display the field or not */
|
|
static int is_enabled (const char *s)
|
|
{
|
|
if (X_option == 1) return !is_unimportant(s);
|
|
|
|
if (c_option) { /* taking the list of disabled fields from the rc file */
|
|
|
|
for (cnf_listnode = cnf_listhead; cnf_listnode; cnf_listnode = cnf_listnode -> next) {
|
|
if (!strcmp(s, cnf_listnode -> description)) return 1;
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static void print_extended_maps (FILE *f)
|
|
{
|
|
char perms[DETAIL_LENGTH], map_desc[128],
|
|
detail_desc[DETAIL_LENGTH], value_str[NUM_LENGTH],
|
|
start[NUM_LENGTH], end[NUM_LENGTH],
|
|
offset[NUM_LENGTH], inode[NUM_LENGTH],
|
|
dev[64], vmflags[VMFLAGS_LENGTH];
|
|
int maxw1=0, maxw2=0, maxw3=0, maxw4=0, maxw5=0, maxwv=0;
|
|
int nfields, firstmapping, footer_gap, i, maxw_;
|
|
char *ret, *map_basename, c, has_vmflags = 0;
|
|
|
|
ret = fgets(mapbuf, sizeof mapbuf, f);
|
|
firstmapping = 2;
|
|
while (ret != NULL) {
|
|
/* === READ MAPPING === */
|
|
map_desc[0] = '\0';
|
|
c = '\n';
|
|
nfields = sscanf(mapbuf,
|
|
"%"NUML"[0-9a-f]-%"NUML"[0-9a-f] "
|
|
"%"DETL"s %"NUML"[0-9a-f] "
|
|
"%63[0-9a-f:] %"NUML"s %127[^\n]%c",
|
|
start, end, perms, offset,
|
|
dev, inode, map_desc, &c);
|
|
/* Must read at least up to inode, else something has changed! */
|
|
if (nfields < 6)
|
|
xerrx(EXIT_FAILURE, _("Unknown format in smaps file!"));
|
|
/* If line too long we dump everything else. */
|
|
while (c != '\n') {
|
|
ret = fgets(mapbuf, sizeof mapbuf, f);
|
|
c = mapbuf[strlen(mapbuf) - 1];
|
|
}
|
|
|
|
/* Store maximum widths for printing nice later */
|
|
if (strlen(start ) > maxw1) maxw1 = strlen(start);
|
|
if (strlen(perms ) > maxw2) maxw2 = strlen(perms);
|
|
if (strlen(offset) > maxw3) maxw3 = strlen(offset);
|
|
if (strlen(dev ) > maxw4) maxw4 = strlen(dev);
|
|
if (strlen(inode ) > maxw5) maxw5 = strlen(inode);
|
|
|
|
ret = fgets(mapbuf, sizeof mapbuf, f);
|
|
nfields = sscanf(mapbuf, "%"DETL"[^:]: %"NUML"[0-9] kB %c",
|
|
detail_desc, value_str, &c);
|
|
listnode = listhead;
|
|
/* === READ MAPPING DETAILS === */
|
|
while (ret != NULL && nfields == 2) {
|
|
|
|
if (!is_enabled(detail_desc)) goto loop_end;
|
|
|
|
/* === CREATE LIST AND FILL description FIELD === */
|
|
if (listnode == NULL) {
|
|
assert(firstmapping == 2);
|
|
listnode = calloc(1, sizeof *listnode);
|
|
if (listhead == NULL) {
|
|
assert(listtail == NULL);
|
|
listhead = listnode;
|
|
} else {
|
|
listtail->next = listnode;
|
|
}
|
|
listtail = listnode;
|
|
/* listnode was calloc()ed so all fields are already NULL! */
|
|
strcpy(listnode->description, detail_desc);
|
|
if (!q_option) listnode->max_width = strlen(detail_desc);
|
|
else listnode->max_width = 0;
|
|
} else {
|
|
/* === LIST EXISTS === */
|
|
if (strcmp(listnode->description, detail_desc) != 0)
|
|
xerrx(EXIT_FAILURE, "ERROR: %s %s",
|
|
_("inconsistent detail field in smaps file, line:\n"),
|
|
mapbuf);
|
|
}
|
|
strcpy(listnode->value_str, value_str);
|
|
sscanf(value_str, "%"KLF"u", &listnode->value);
|
|
if (firstmapping == 2) {
|
|
listnode->total += listnode->value;
|
|
if (q_option) {
|
|
maxw_ = strlen(listnode->value_str);
|
|
if (maxw_ > listnode->max_width)
|
|
listnode->max_width = maxw_;
|
|
}
|
|
}
|
|
listnode = listnode->next;
|
|
loop_end:
|
|
ret = fgets(mapbuf, sizeof mapbuf, f);
|
|
nfields = sscanf(mapbuf, "%"DETL"[^:]: %"NUML"[0-9] kB %c",
|
|
detail_desc, value_str, &c);
|
|
}
|
|
|
|
/* === GET VMFLAGS === */
|
|
nfields = sscanf(mapbuf, "VmFlags: %[a-z ]", vmflags);
|
|
if (nfields == 1) {
|
|
if (! has_vmflags) has_vmflags = 1;
|
|
ret = fgets(mapbuf, sizeof mapbuf, f);
|
|
if (strlen(vmflags) > maxwv) maxwv = strlen(vmflags);
|
|
}
|
|
|
|
if (firstmapping == 2) { /* width measurement stage, do not print anything yet */
|
|
if (ret == NULL) { /* once the end of file is reached ...*/
|
|
firstmapping = 1; /* ... we reset the file position to the beginning of the file */
|
|
fseek(f, 0, SEEK_SET); /* ... and repeat the process with printing enabled */
|
|
ret = fgets(mapbuf, sizeof mapbuf, f); /* this is not ideal and needs to be redesigned one day */
|
|
|
|
if (!q_option) {
|
|
/* calculate width of totals */
|
|
for (listnode=listhead; listnode!=NULL; listnode=listnode->next) {
|
|
maxw_ = integer_width(listnode->total);
|
|
if (maxw_ > listnode->max_width)
|
|
listnode->max_width = maxw_;
|
|
}
|
|
}
|
|
|
|
}
|
|
} else { /* the maximum widths have been measured, we've already reached the printing stage */
|
|
/* === PRINT THIS MAPPING === */
|
|
|
|
/* Print header */
|
|
if (firstmapping && !q_option) {
|
|
|
|
maxw1 = justify_print(nls_Address, maxw1, 1);
|
|
|
|
if (is_enabled(nls_Perm))
|
|
maxw2 = justify_print(nls_Perm, maxw2, 1);
|
|
|
|
if (is_enabled(nls_Offset))
|
|
maxw3 = justify_print(nls_Offset, maxw3, 1);
|
|
|
|
if (is_enabled(nls_Device))
|
|
maxw4 = justify_print(nls_Device, maxw4, 1);
|
|
|
|
if (is_enabled(nls_Inode))
|
|
maxw5 = justify_print(nls_Inode, maxw5, 1);
|
|
|
|
for (listnode=listhead; listnode!=NULL; listnode=listnode->next)
|
|
justify_print(listnode->description, listnode->max_width, 1);
|
|
|
|
if (has_vmflags && is_enabled("VmFlags"))
|
|
printf(" %*s", maxwv, "VmFlags");
|
|
|
|
if (is_enabled(nls_Mapping))
|
|
justify_print(nls_Mapping, 0, 0);
|
|
else
|
|
printf("\n");
|
|
}
|
|
|
|
/* Print data */
|
|
printf("%*s", maxw1, start); /* Address field is always enabled */
|
|
|
|
if (is_enabled(nls_Perm))
|
|
printf(" %*s", maxw2, perms);
|
|
|
|
if (is_enabled(nls_Offset))
|
|
printf(" %*s", maxw3, offset);
|
|
|
|
if (is_enabled(nls_Device))
|
|
printf(" %*s", maxw4, dev);
|
|
|
|
if (is_enabled(nls_Inode))
|
|
printf(" %*s", maxw5, inode);
|
|
|
|
for (listnode=listhead; listnode!=NULL; listnode=listnode->next)
|
|
printf(" %*s", listnode->max_width, listnode->value_str);
|
|
|
|
if (has_vmflags && is_enabled("VmFlags"))
|
|
printf(" %*s", maxwv, vmflags);
|
|
|
|
if (is_enabled(nls_Mapping)) {
|
|
if (map_desc_showpath) {
|
|
printf(" %s", map_desc);
|
|
} else {
|
|
map_basename = strrchr(map_desc, '/');
|
|
if (!map_basename) {
|
|
printf(" %s", map_desc);
|
|
} else {
|
|
printf(" %s", map_basename + 1);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
firstmapping = 0;
|
|
|
|
}
|
|
}
|
|
/* === PRINT TOTALS === */
|
|
if (!q_option && listhead!=NULL) { /* footer enabled and non-empty */
|
|
|
|
footer_gap = maxw1 + 1; /* Address field is always enabled */
|
|
if (is_enabled(nls_Perm )) footer_gap += maxw2 + 1;
|
|
if (is_enabled(nls_Offset)) footer_gap += maxw3 + 1;
|
|
if (is_enabled(nls_Device)) footer_gap += maxw4 + 1;
|
|
if (is_enabled(nls_Inode )) footer_gap += maxw5 + 1;
|
|
|
|
for (i=0; i<footer_gap; i++) putc(' ', stdout);
|
|
|
|
for (listnode=listhead; listnode!=NULL; listnode=listnode->next) {
|
|
for (i=0; i<listnode->max_width; i++)
|
|
putc('=', stdout);
|
|
putc(' ', stdout);
|
|
}
|
|
|
|
putc('\n', stdout);
|
|
|
|
for (i=0; i<footer_gap; i++) putc(' ', stdout);
|
|
|
|
for (listnode=listhead; listnode!=NULL; listnode=listnode->next)
|
|
printf("%*lu ", listnode->max_width, listnode->total);
|
|
|
|
fputs("KB \n", stdout);
|
|
}
|
|
/* We don't free() the list, it's used for all PIDs passed as arguments */
|
|
}
|
|
|
|
static int one_proc(proc_t * p)
|
|
{
|
|
char buf[32];
|
|
FILE *fp;
|
|
unsigned long total_shared = 0ul;
|
|
unsigned long total_private_readonly = 0ul;
|
|
unsigned long total_private_writeable = 0ul;
|
|
KLONG diff = 0;
|
|
const char *cp2 = NULL;
|
|
unsigned long long rss = 0ull;
|
|
unsigned long long private_dirty = 0ull;
|
|
unsigned long long shared_dirty = 0ull;
|
|
unsigned long long total_rss = 0ull;
|
|
unsigned long long total_private_dirty = 0ull;
|
|
unsigned long long total_shared_dirty = 0ull;
|
|
int maxw1=0, maxw2=0, maxw3=0, maxw4=0, maxw5=0;
|
|
|
|
/* Overkill, but who knows what is proper? The "w" prog uses
|
|
* the tty width to determine this.
|
|
*/
|
|
int maxcmd = 0xfffff;
|
|
|
|
escape_command(cmdbuf, p, sizeof cmdbuf, &maxcmd,
|
|
ESC_ARGS | ESC_BRACKETS);
|
|
printf("%u: %s\n", p->tgid, cmdbuf);
|
|
|
|
if (x_option || X_option || c_option) {
|
|
sprintf(buf, "/proc/%u/smaps", p->tgid);
|
|
if ((fp = fopen(buf, "r")) == NULL)
|
|
return 1;
|
|
} else {
|
|
sprintf(buf, "/proc/%u/maps", p->tgid);
|
|
if ((fp = fopen(buf, "r")) == NULL)
|
|
return 1;
|
|
}
|
|
|
|
if (X_option || c_option) {
|
|
print_extended_maps(fp);
|
|
return 0;
|
|
}
|
|
|
|
if (x_option) {
|
|
maxw1 = 16;
|
|
if (sizeof(KLONG) == 4) maxw1 = 8;
|
|
maxw2 = maxw3 = maxw4 = 7;
|
|
maxw5 = 5;
|
|
if (!q_option) {
|
|
maxw1 = justify_print(nls_Address, maxw1, 0);
|
|
maxw2 = justify_print(nls_Kbytes, maxw2, 1);
|
|
maxw3 = justify_print(nls_RSS, maxw3, 1);
|
|
maxw4 = justify_print(nls_Dirty, maxw4, 1);
|
|
maxw5 = justify_print(nls_Mode, maxw5, 0);
|
|
justify_print(nls_Mapping, 0, 0);
|
|
}
|
|
}
|
|
|
|
if (d_option) {
|
|
maxw1 = 16;
|
|
if (sizeof(KLONG) == 4) maxw1 = 8;
|
|
maxw2 = 7;
|
|
maxw3 = 5;
|
|
maxw4 = 16;
|
|
maxw5 = 9;
|
|
if (!q_option) {
|
|
maxw1 = justify_print(nls_Address, maxw1, 0);
|
|
maxw2 = justify_print(nls_Kbytes, maxw2, 1);
|
|
maxw3 = justify_print(nls_Mode, maxw3, 0);
|
|
maxw4 = justify_print(nls_Offset, maxw4, 0);
|
|
maxw5 = justify_print(nls_Device, maxw5, 0);
|
|
justify_print(nls_Mapping, 0, 0);
|
|
}
|
|
}
|
|
|
|
while (fgets(mapbuf, sizeof mapbuf, fp)) {
|
|
char perms[32];
|
|
/* to clean up unprintables */
|
|
char *tmp;
|
|
unsigned KLONG start, end;
|
|
unsigned long long file_offset, inode;
|
|
unsigned dev_major, dev_minor;
|
|
unsigned long long smap_value;
|
|
char smap_key[21];
|
|
|
|
/* hex values are lower case or numeric, keys are upper */
|
|
if (mapbuf[0] >= 'A' && mapbuf[0] <= 'Z') {
|
|
/* Its a key */
|
|
if (sscanf
|
|
(mapbuf, "%20[^:]: %llu", smap_key,
|
|
&smap_value) == 2) {
|
|
if (strncmp("Rss", smap_key, 3) == 0) {
|
|
rss = smap_value;
|
|
total_rss += smap_value;
|
|
continue;
|
|
}
|
|
if (strncmp("Shared_Dirty", smap_key, 12) == 0) {
|
|
shared_dirty = smap_value;
|
|
total_shared_dirty += smap_value;
|
|
continue;
|
|
}
|
|
if (strncmp("Private_Dirty", smap_key, 13) == 0) {
|
|
private_dirty = smap_value;
|
|
total_private_dirty += smap_value;
|
|
continue;
|
|
}
|
|
if (strncmp("Swap", smap_key, 4) == 0) {
|
|
/*doesn't matter as long as last */
|
|
printf("%0*" KLF "x %*lu %*llu %*llu %*s %s\n",
|
|
maxw1, start,
|
|
maxw2, (unsigned long)(diff >> 10),
|
|
maxw3, rss,
|
|
maxw4, (private_dirty + shared_dirty),
|
|
maxw5, perms,
|
|
cp2);
|
|
/* reset some counters */
|
|
rss = shared_dirty = private_dirty = 0ull;
|
|
diff = 0;
|
|
continue;
|
|
}
|
|
/* Other keys */
|
|
continue;
|
|
}
|
|
}
|
|
sscanf(mapbuf, "%" KLF "x-%" KLF "x %31s %llx %x:%x %llu", &start,
|
|
&end, perms, &file_offset, &dev_major, &dev_minor,
|
|
&inode);
|
|
|
|
if (end - 1 < range_low)
|
|
continue;
|
|
if (range_high < start)
|
|
break;
|
|
|
|
tmp = strchr(mapbuf, '\n');
|
|
if (tmp)
|
|
*tmp = '\0';
|
|
tmp = mapbuf;
|
|
while (*tmp) {
|
|
if (!isprint(*tmp))
|
|
*tmp = '?';
|
|
tmp++;
|
|
}
|
|
|
|
diff = end - start;
|
|
if (perms[3] == 's')
|
|
total_shared += diff;
|
|
if (perms[3] == 'p') {
|
|
perms[3] = '-';
|
|
if (perms[1] == 'w')
|
|
total_private_writeable += diff;
|
|
else
|
|
total_private_readonly += diff;
|
|
}
|
|
/* format used by Solaris 9 and procps-3.2.0+ an 'R'
|
|
* if swap not reserved (MAP_NORESERVE, SysV ISM
|
|
* shared mem, etc.)
|
|
*/
|
|
perms[4] = '-';
|
|
perms[5] = '\0';
|
|
|
|
if (x_option) {
|
|
cp2 =
|
|
mapping_name(p, start, diff, mapbuf, map_desc_showpath, dev_major,
|
|
dev_minor, inode);
|
|
/* printed with the keys */
|
|
continue;
|
|
}
|
|
if (d_option) {
|
|
const char *cp =
|
|
mapping_name(p, start, diff, mapbuf, map_desc_showpath, dev_major,
|
|
dev_minor, inode);
|
|
printf("%0*" KLF "x %*lu %*s %0*llx %*.*s%03x:%05x %s\n",
|
|
maxw1, start,
|
|
maxw2, (unsigned long)(diff >> 10),
|
|
maxw3, perms,
|
|
maxw4, file_offset,
|
|
(maxw5-9), (maxw5-9), " ", dev_major, dev_minor,
|
|
cp);
|
|
}
|
|
if (!x_option && !d_option) {
|
|
const char *cp =
|
|
mapping_name(p, start, diff, mapbuf, map_desc_showpath, dev_major,
|
|
dev_minor, inode);
|
|
printf((sizeof(KLONG) == 8)
|
|
? "%016" KLF "x %6luK %s %s\n"
|
|
: "%08lx %6luK %s %s\n",
|
|
start, (unsigned long)(diff >> 10), perms, cp);
|
|
}
|
|
|
|
}
|
|
fclose(fp);
|
|
if (!q_option) {
|
|
if (x_option) {
|
|
if (sizeof(KLONG) == 4)
|
|
justify_print("--------", maxw1, 0);
|
|
else
|
|
justify_print("----------------", maxw1, 0);
|
|
justify_print("-------", maxw2, 1);
|
|
justify_print("-------", maxw3, 1);
|
|
justify_print("-------", maxw4, 1);
|
|
printf("\n");
|
|
|
|
printf("%-*s ", maxw1, _("total kB"));
|
|
printf("%*ld %*llu %*llu\n",
|
|
maxw2, (total_shared +
|
|
total_private_writeable +
|
|
total_private_readonly) >> 10,
|
|
maxw3, total_rss,
|
|
maxw4, (total_shared_dirty +
|
|
total_private_dirty));
|
|
}
|
|
if (d_option) {
|
|
printf
|
|
(_("mapped: %ldK writeable/private: %ldK shared: %ldK\n"),
|
|
(total_shared + total_private_writeable +
|
|
total_private_readonly) >> 10,
|
|
total_private_writeable >> 10, total_shared >> 10);
|
|
}
|
|
if (!x_option && !d_option) {
|
|
if (sizeof(KLONG) == 8)
|
|
/* Translation Hint: keep total string length
|
|
* as 24 characters. Adjust %16 if needed*/
|
|
printf(_(" total %16ldK\n"),
|
|
(total_shared + total_private_writeable +
|
|
total_private_readonly) >> 10);
|
|
else
|
|
/* Translation Hint: keep total string length
|
|
* as 16 characters. Adjust %8 if needed*/
|
|
printf(_(" total %8ldK\n"),
|
|
(total_shared + total_private_writeable +
|
|
total_private_readonly) >> 10);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void range_arguments(char *optarg)
|
|
{
|
|
char *arg1;
|
|
char *arg2;
|
|
|
|
arg1 = xstrdup(optarg);
|
|
arg2 = strchr(arg1, ',');
|
|
if (arg2)
|
|
*arg2 = '\0';
|
|
if (arg2)
|
|
++arg2;
|
|
else
|
|
arg2 = arg1;
|
|
if (arg1 && *arg1)
|
|
range_low = STRTOUKL(arg1, &arg1, 16);
|
|
if (*arg2)
|
|
range_high = STRTOUKL(arg2, &arg2, 16);
|
|
if (arg1 && (*arg1 || *arg2))
|
|
xerrx(EXIT_FAILURE, "%s: '%s'", _("failed to parse argument"),
|
|
optarg);
|
|
}
|
|
|
|
|
|
#define MAX_CNF_LINE_LEN 1024
|
|
|
|
#define SECTION_ID_NONE 0
|
|
#define SECTION_ID_UNSUPPORTED 1
|
|
|
|
#define SECTION_STR_FIELDS_DISPLAY "[Fields Display]"
|
|
#define SECTION_STR_FIELDS_DISPLAY_LEN (sizeof(SECTION_STR_FIELDS_DISPLAY) - 1)
|
|
#define SECTION_ID_FIELDS_DISPLAY 2
|
|
|
|
#define SECTION_STR_MAPPING "[Mapping]"
|
|
#define SECTION_STR_MAPPING_LEN (sizeof(SECTION_STR_MAPPING) - 1)
|
|
#define SECTION_ID_MAPPING 3
|
|
|
|
static int config_read (char *rc_filename)
|
|
{
|
|
FILE *f;
|
|
char line_buf[MAX_CNF_LINE_LEN + 1];
|
|
char tmp_buf [MAX_CNF_LINE_LEN + 1];
|
|
char *trimmed;
|
|
int length;
|
|
char *tail, *token;
|
|
int line_cnt, section_id;
|
|
|
|
f = fopen(rc_filename, "r");
|
|
|
|
if (!f) return 0; /* can't open the file for reading */
|
|
|
|
line_cnt = 0;
|
|
section_id = SECTION_ID_NONE;
|
|
|
|
while (fgets (line_buf, MAX_CNF_LINE_LEN + 1, f)) {
|
|
|
|
line_cnt++;
|
|
|
|
/* get rid of the LF char */
|
|
length = strlen(line_buf);
|
|
if (length > 0 && line_buf[length - 1] == '\n') {
|
|
line_buf[length - 1] = '\0';
|
|
} else if (length == MAX_CNF_LINE_LEN) { /* no LF char -> line too long */
|
|
xwarnx(_("config line too long - line %d"), line_cnt);
|
|
/* ignoring the tail */
|
|
while (fgets (tmp_buf, MAX_CNF_LINE_LEN + 1, f) &&
|
|
(length = strlen(tmp_buf))>0 &&
|
|
tmp_buf[length - 1] != '\n') ;
|
|
}
|
|
|
|
/* trim leading whitespaces */
|
|
trimmed = line_buf;
|
|
while (*trimmed == ' ' || *trimmed == '\t') trimmed++;
|
|
|
|
/* skip comments and empty lines */
|
|
if (*trimmed == '#' || *trimmed == '\0') continue;
|
|
|
|
if (*trimmed == '[') { /* section */
|
|
if (!strncmp(trimmed, SECTION_STR_FIELDS_DISPLAY, SECTION_STR_FIELDS_DISPLAY_LEN)) {
|
|
trimmed += SECTION_STR_FIELDS_DISPLAY_LEN;
|
|
section_id = SECTION_ID_FIELDS_DISPLAY;
|
|
} else if (!strncmp(trimmed, SECTION_STR_MAPPING, SECTION_STR_MAPPING_LEN)) {
|
|
trimmed += SECTION_STR_MAPPING_LEN;
|
|
section_id = SECTION_ID_MAPPING;
|
|
} else {
|
|
while (*trimmed != ']' || *trimmed == '\0') trimmed++;
|
|
if (*trimmed == ']') {
|
|
section_id = SECTION_ID_UNSUPPORTED;
|
|
xwarnx(_("unsupported section found in the config - line %d"), line_cnt);
|
|
trimmed++;
|
|
} else {
|
|
xwarnx(_("syntax error found in the config - line %d"), line_cnt);
|
|
}
|
|
}
|
|
|
|
/* trim trailing whitespaces */
|
|
while (*trimmed == ' ' || *trimmed == '\t') trimmed++;
|
|
|
|
/* skip comments and empty tails */
|
|
if (*trimmed == '#' || *trimmed == '\0') continue;
|
|
|
|
/* anything else found on the section line ??? */
|
|
xwarnx(_("syntax error found in the config - line %d"), line_cnt);
|
|
}
|
|
|
|
switch (section_id) {
|
|
case SECTION_ID_FIELDS_DISPLAY:
|
|
token = strtok (trimmed, " \t");
|
|
|
|
if (token) {
|
|
tail = strtok (NULL, " \t");
|
|
|
|
if (tail && *tail != '#') {
|
|
xwarnx(_("syntax error found in the config - line %d"), line_cnt);
|
|
}
|
|
|
|
/* add the field in the list */
|
|
cnf_listnode = calloc(1, sizeof *cnf_listnode);
|
|
snprintf(cnf_listnode -> description, sizeof(cnf_listnode -> description), "%s", token);
|
|
cnf_listnode -> next = cnf_listhead;
|
|
cnf_listhead = cnf_listnode;
|
|
}
|
|
|
|
break;
|
|
|
|
case SECTION_ID_MAPPING:
|
|
token = strtok (trimmed, " \t");
|
|
|
|
if (token) {
|
|
tail = strtok (NULL, " \t");
|
|
|
|
if (tail && *tail != '#') {
|
|
xwarnx(_("syntax error found in the config - line %d"), line_cnt);
|
|
}
|
|
|
|
if (!strcmp(token,"ShowPath")) map_desc_showpath = !map_desc_showpath;
|
|
}
|
|
|
|
break;
|
|
|
|
case SECTION_ID_UNSUPPORTED:
|
|
break; /* ignore the content */
|
|
|
|
default:
|
|
xwarnx(_("syntax error found in the config - line %d"), line_cnt);
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int config_create (char *rc_filename)
|
|
{
|
|
FILE *f;
|
|
|
|
/* check if rc exists */
|
|
f = fopen(rc_filename, "r");
|
|
|
|
if (f) { /* file exists ... let user to delete/remove it first */
|
|
fclose(f);
|
|
xwarnx(_("the file already exists - delete or rename it first"));
|
|
return 0;
|
|
}
|
|
|
|
/* file doesn't exist */
|
|
|
|
f = fopen(rc_filename, "w");
|
|
|
|
if (!f) return 0; /* can't open the file for writing */
|
|
|
|
/* current rc template, might change in the future */
|
|
fprintf(f,"# pmap's Config File\n");
|
|
fprintf(f,"\n");
|
|
fprintf(f,"# All the entries are case sensitive.\n");
|
|
fprintf(f,"# Unsupported entries are ignored!\n");
|
|
fprintf(f,"\n");
|
|
fprintf(f,"[Fields Display]\n");
|
|
fprintf(f,"\n");
|
|
fprintf(f,"# To enable a field uncomment its entry\n");
|
|
fprintf(f,"\n");
|
|
fprintf(f,"#%s\n", nls_Perm);
|
|
fprintf(f,"#%s\n", nls_Offset);
|
|
fprintf(f,"#%s\n", nls_Device);
|
|
fprintf(f,"#%s\n", nls_Inode);
|
|
fprintf(f,"#Size\n");
|
|
fprintf(f,"#Rss\n");
|
|
fprintf(f,"#Pss\n");
|
|
fprintf(f,"#Shared_Clean\n");
|
|
fprintf(f,"#Shared_Dirty\n");
|
|
fprintf(f,"#Private_Clean\n");
|
|
fprintf(f,"#Private_Dirty\n");
|
|
fprintf(f,"#Referenced\n");
|
|
fprintf(f,"#Anonymous\n");
|
|
fprintf(f,"#AnonHugePages\n");
|
|
fprintf(f,"#Swap\n");
|
|
fprintf(f,"#KernelPageSize\n");
|
|
fprintf(f,"#MMUPageSize\n");
|
|
fprintf(f,"#Locked\n");
|
|
fprintf(f,"#VmFlags\n");
|
|
fprintf(f,"#%s\n", nls_Mapping);
|
|
fprintf(f,"\n");
|
|
fprintf(f,"\n");
|
|
fprintf(f,"[Mapping]\n");
|
|
fprintf(f,"\n");
|
|
fprintf(f,"# to show paths in the mapping column uncomment the following line\n");
|
|
fprintf(f,"#ShowPath\n");
|
|
fprintf(f,"\n");
|
|
|
|
fclose(f);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* returns rc filename based on the program_invocation_short_name */
|
|
static char *get_default_rc_filename(void)
|
|
{
|
|
char *rc_filename;
|
|
int rc_filename_len;
|
|
const char *homedir;
|
|
|
|
homedir = getenv("HOME");
|
|
if (!homedir) {
|
|
xwarnx(_("HOME variable undefined"));
|
|
return NULL;
|
|
}
|
|
|
|
rc_filename_len = snprintf(NULL, 0, "%s/.%src", homedir, program_invocation_short_name);
|
|
|
|
rc_filename = (char *) calloc (1, rc_filename_len + 1);
|
|
if (!rc_filename) {
|
|
xwarnx(_("memory allocation failed"));
|
|
return NULL;
|
|
}
|
|
|
|
snprintf(rc_filename, rc_filename_len + 1, "%s/.%src", homedir, program_invocation_short_name);
|
|
|
|
return rc_filename;
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
pid_t *pidlist;
|
|
unsigned count = 0;
|
|
PROCTAB *PT;
|
|
proc_t p;
|
|
int ret = 0, c, conf_ret;
|
|
char *rc_filename = NULL;
|
|
|
|
static const struct option longopts[] = {
|
|
{"extended", no_argument, NULL, 'x'},
|
|
{"device", no_argument, NULL, 'd'},
|
|
{"quiet", no_argument, NULL, 'q'},
|
|
{"range", required_argument, NULL, 'A'},
|
|
{"help", no_argument, NULL, 'h'},
|
|
{"version", no_argument, NULL, 'V'},
|
|
{"read-rc", no_argument, NULL, 'c'},
|
|
{"read-rc-from", required_argument, NULL, 'C'},
|
|
{"create-rc", no_argument, NULL, 'n'},
|
|
{"create-rc-to", required_argument, NULL, 'N'},
|
|
{"show-path", no_argument, NULL, 'p'},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
#ifdef HAVE_PROGRAM_INVOCATION_NAME
|
|
program_invocation_name = program_invocation_short_name;
|
|
#endif
|
|
nls_initialize();
|
|
atexit(close_stdout);
|
|
|
|
if (argc < 2)
|
|
usage(stderr);
|
|
|
|
while ((c = getopt_long(argc, argv, "xXrdqA:hVcC:nN:p", longopts, NULL)) != -1)
|
|
switch (c) {
|
|
case 'x':
|
|
x_option = 1;
|
|
break;
|
|
case 'X':
|
|
X_option++;
|
|
break;
|
|
case 'r':
|
|
xwarnx(_("option -r is ignored as SunOS compatibility"));
|
|
break;
|
|
case 'd':
|
|
d_option = 1;
|
|
break;
|
|
case 'q':
|
|
q_option = 1;
|
|
break;
|
|
case 'A':
|
|
range_arguments(optarg);
|
|
break;
|
|
case 'h':
|
|
usage(stdout);
|
|
case 'V':
|
|
printf(PROCPS_NG_VERSION);
|
|
return EXIT_SUCCESS;
|
|
case 'c':
|
|
c_option = 1;
|
|
break;
|
|
case 'C':
|
|
C_option = 1;
|
|
rc_filename = optarg;
|
|
break;
|
|
case 'n':
|
|
n_option = 1;
|
|
break;
|
|
case 'N':
|
|
N_option = 1;
|
|
rc_filename = optarg;
|
|
break;
|
|
case 'p':
|
|
map_desc_showpath = 1;
|
|
break;
|
|
case 'a': /* Sun prints anon/swap reservations */
|
|
case 'F': /* Sun forces hostile ptrace-like grab */
|
|
case 'l': /* Sun shows unresolved dynamic names */
|
|
case 'L': /* Sun shows lgroup info */
|
|
case 's': /* Sun shows page sizes */
|
|
case 'S': /* Sun shows swap reservations */
|
|
default:
|
|
usage(stderr);
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (c_option + C_option + d_option + n_option + N_option + x_option + !!X_option > 1)
|
|
xerrx(EXIT_FAILURE, _("options -c, -C, -d, -n, -N, -x, -X are mutually exclusive"));
|
|
|
|
if ((n_option || N_option) && (q_option || map_desc_showpath))
|
|
xerrx(EXIT_FAILURE, _("options -p, -q are mutually exclusive with -n, -N"));
|
|
|
|
if ((n_option || N_option) && argc > 0)
|
|
xerrx(EXIT_FAILURE, _("too many arguments"));
|
|
|
|
if (N_option) {
|
|
if (config_create(rc_filename)) {
|
|
xwarnx(_("rc file successfully created, feel free to edit the content"));
|
|
return (EXIT_SUCCESS);
|
|
} else {
|
|
xerrx(EXIT_FAILURE, _("couldn't create the rc file"));
|
|
}
|
|
}
|
|
|
|
if (n_option) {
|
|
rc_filename = get_default_rc_filename();
|
|
|
|
if (!rc_filename) return(EXIT_FAILURE);
|
|
|
|
conf_ret = config_create(rc_filename); free(rc_filename);
|
|
|
|
if (conf_ret) {
|
|
xwarnx(_("~/.%src file successfully created, feel free to edit the content"), program_invocation_short_name);
|
|
return (EXIT_SUCCESS);
|
|
} else {
|
|
xerrx(EXIT_FAILURE, _("couldn't create ~/.%src"), program_invocation_short_name);
|
|
}
|
|
}
|
|
|
|
if (argc < 1)
|
|
xerrx(EXIT_FAILURE, _("argument missing"));
|
|
|
|
if (C_option) c_option = 1;
|
|
|
|
if (c_option) {
|
|
|
|
if (!C_option) rc_filename = get_default_rc_filename();
|
|
|
|
if (!rc_filename) return(EXIT_FAILURE);
|
|
|
|
conf_ret = config_read(rc_filename);
|
|
|
|
if (!conf_ret) {
|
|
if (C_option) {
|
|
xerrx(EXIT_FAILURE, _("couldn't read the rc file"));
|
|
} else {
|
|
xwarnx(_("couldn't read ~/.%src"), program_invocation_short_name);
|
|
free(rc_filename);
|
|
return(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
pidlist = xmalloc(sizeof(pid_t) * (argc+1));
|
|
|
|
while (*argv) {
|
|
char *walk = *argv++;
|
|
char *endp;
|
|
unsigned long pid;
|
|
if (!strncmp("/proc/", walk, 6)) {
|
|
walk += 6;
|
|
/* user allowed to do: pmap /proc/PID */
|
|
if (*walk < '0' || *walk > '9')
|
|
continue;
|
|
}
|
|
if (*walk < '0' || *walk > '9')
|
|
usage(stderr);
|
|
pid = strtoul(walk, &endp, 0);
|
|
if (pid < 1ul || pid > 0x7ffffffful || *endp)
|
|
usage(stderr);
|
|
pidlist[count++] = pid;
|
|
}
|
|
|
|
discover_shm_minor();
|
|
|
|
memset(&p, '\0', sizeof(p));
|
|
/* old libproc interface is zero-terminated */
|
|
pidlist[count] = 0;
|
|
PT = openproc(PROC_FILLSTAT | PROC_FILLARG | PROC_PID, pidlist);
|
|
while (readproc(PT, &p)) {
|
|
ret |= one_proc(&p);
|
|
count--;
|
|
}
|
|
closeproc(PT);
|
|
free(pidlist);
|
|
|
|
/* cleaning the list used for the -c/-X/-XX modes */
|
|
for (listnode = listhead; listnode != NULL ; ) {
|
|
listnode = listnode -> next;
|
|
free(listhead);
|
|
listhead = listnode;
|
|
}
|
|
|
|
/* cleaning the list used for the -c mode */
|
|
for (cnf_listnode = cnf_listhead; cnf_listnode != NULL ; ) {
|
|
cnf_listnode = cnf_listnode -> next;
|
|
free(cnf_listhead);
|
|
cnf_listhead = cnf_listnode;
|
|
}
|
|
|
|
if (count)
|
|
/* didn't find all processes asked for */
|
|
ret |= 42;
|
|
return ret;
|
|
}
|