/*
 * 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;
}