text: /*
   * 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';
  		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;
  
  	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;
  	}
  
  	escape_command(cmdbuf, p, sizeof cmdbuf, &maxcmd,
  		       ESC_ARGS | ESC_BRACKETS);
  	printf("%u:   %s\n", p->tgid, cmdbuf);
  
  	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) {
  					/*doesnt 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;
  }
  