/*
 * pmap - print the address space map of a process
 *
 * Chris Rivera		<cmrivera@ufl.edu>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, v2, as
 * published by the Free Software Foundation
 *
 * Copyright (C) 2003 Chris Rivera
 */

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <getopt.h>

#include "proc/readproc.h"
#include "proc/sysinfo.h"
#include "proc/version.h"
#include "proc/procps.h"

#define BUFFERSIZE	1024
#define OBJECTSIZE	80

unsigned long mapped = 0;
unsigned long shared = 0;
unsigned long private = 0;

void usage(const char *cmd)
{
	fprintf(stderr, "usage: %s [options] pid\n", cmd);
	fprintf(stderr, "  -d, --device         "
			"display offset and device numbers\n");
	fprintf(stderr, "  -q, --quiet          "
			"hide header and memory statistics\n");
	fprintf(stderr, "  -V, --version        "
			"display version information\n");
	fprintf(stderr, "  -h, --help           "
			"display this help\n");
}

void parse_line(const char *line, int show_devices)
{
	int assigned;
	unsigned int low, high, size, offset, major, minor;
	char read_perm, write_perm, exec_perm, access_type;
	char obj_buff[OBJECTSIZE] = "[ anon ]";

	assigned = sscanf(line, "%x-%x %c%c%c%c %x %x:%x %*u %" 
		   STRINGIFY(OBJECTSIZE) "s", &low, &high, &read_perm,
		   &write_perm, &exec_perm, &access_type, &offset, &major,
		   &minor, obj_buff);

	if (assigned < 9) {
		fprintf(stderr, "failed to parse /proc/<PID>/maps\n");
		exit(EXIT_FAILURE);
	}

	size = (high - low) / 1024;
	mapped += size;
	if (access_type == 's')
		shared += size;
	else if (access_type == 'p' && write_perm == 'w')
		private += size;

	if (show_devices)
		printf("%08x %8dK %c%c%c%c %08x %02x:%02x  %s\n",
		       low, size, read_perm, write_perm, exec_perm,
		       access_type, offset, major, minor, obj_buff);
	else
		printf("%08x %8dK %c%c%c%c %s\n",
			low, size, read_perm, write_perm, exec_perm,
			access_type, obj_buff);
}

int main(int argc, char *argv[])
{
	FILE *fp;
	char path[PATH_MAX];
	char buff[BUFFERSIZE];
	char cmd[PATH_MAX];
	pid_t pid;
	int o, show_devices = 0, quiet = 0;

	struct option longopts[] = {
		{ "help", 	0, NULL, 'h' },
		{ "version", 	0, NULL, 'V' },
		{ "quiet", 	0, NULL, 'q' },
		{ "device", 	0, NULL, 'd' },
		{  NULL,	0, NULL, 0 }
	};

	while ((o = getopt_long(argc, argv, "hqdV", longopts, NULL)) != -1) {
		switch(o) {
			case 'V':
				display_version();
				return 0;
			case 'q':
				quiet = 1;
				break;
			case 'd':
				show_devices = 1;
				break;
			case 'h':
				usage(argv[0]);
				return 0;
			default:
				usage(argv[0]);
				return 1;
		}
	}

	if (argc - optind > 0) {
		errno = 0;
		pid = strtoul(argv[optind], NULL, 10);
		if (errno) {
			perror("strtoul");
			exit(EXIT_FAILURE);
		}
	} else {
		usage(argv[0]);
		return 1;
	}

	if (get_cmdline(pid, cmd, PATH_MAX))
		printf("%d: %s\n", pid, cmd);
	else {
		fprintf(stderr, "error: no such pid\n");
		exit(EXIT_FAILURE);
	}

	snprintf(path, PATH_MAX, "/proc/%d/maps", pid);
	fp = fopen(path, "r");
	if (!fp) {
		perror("fopen");
		exit(EXIT_FAILURE);
	}

	if (!quiet) {
		if (show_devices)
			printf("Start         Size Perm Offset   Device Mapping\n");
		else
			printf("Start         Size Perm Mapping\n");
	}

	while (fgets(buff, BUFFERSIZE - 1, fp))
		parse_line(buff, show_devices);

	if (!quiet)
		printf("mapped: "
		       "%luK    writeable/private: %luK    shared: %luK\n",
		       mapped, private, shared);

	fclose(fp);
	return 0;
}
