/*
 * Copyright (c) 2011, Columbia University
 * All rights reserved.
 *
 * This software was developed by Vasileios P. Kemerlis <vpk@cs.columbia.edu>
 * at Columbia University, New York, NY, USA, in May 2011.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Columbia University nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */


#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* unnecessary and boring */
#define __PROG__	"getPAGEMAP"
#define __EXEC__	"getpmap"
#define __VER__		"19042002be"
#define __COPYLEFT__	"Copyright 2011 Columbia University.\nThis is free software; see the source for copying conditions. There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
#define __BUGS__	"<vpk@cs.columbia.edu>"

/* constants */
#define BASE10		10		/* base 10 */
#define BASE16		16		/* base 16 */
#define PATH_SZ		32		/* path size (/proc/<pid>/pagemap) */
#define PRESENT_MASK	(1ULL << 63) 	/* get bit 63 from a 64-bit integer */
#define PFN_MASK	((1ULL << 55) - 1)	/* get bits 0-54 from
						   a 64-bit integer */


/*
 * help
 *
 * display useful information
 */
static void
help(void)
{
	/* usage info */
	(void)fprintf(stdout, "Usage: %s [OPTION]...\n", __EXEC__);
	(void)fprintf(stdout, "Read /proc/<pid>/pagemap.\n\n");

	/* options */
	(void)fprintf(stdout,
	"\t-p, --pid=NUM\t\tread the pagemap of process with PID=NUM\n");
	(void)fprintf(stdout,
	"\t-a, --virt=NUM\t\tread the pagemap entry for virtual address=NUM\n");
	(void)fprintf(stdout, "\t-h, --help\t\tdisplay this help and exit\n");
	(void)fprintf(stdout,
		"\t-v, --version\t\tprint version information and exit\n\n");

	/* bugs */
	(void)fprintf(stdout, "Report bugs to %s\n", __BUGS__);
} 

/*
 * version
 *
 * display version information
 */
static void
version(void)
{
	/* display version */
	(void)fprintf(stdout, "%s %s\n\n", __PROG__, __VER__);
	/* copyright info */
	(void)fprintf(stdout, "%s\n", __COPYLEFT__); 
}

/*
 * query the pagemap
 *
 * open the /proc/<pid>/pagemap of a process and search the page frame
 * information for a specific virtual address
 *
 * @pid:	the pid of the process that we are interested into
 * @vaddr:	the virtual address (page-aligned)
 * @psize:	page size
 */
static void
querypmap(pid_t pid, unsigned long vaddr, long psize)
{
	/* path in /proc */
	char		path[PATH_SZ];
	/* pagemap entry */
	uint64_t	pentry	= 0;

	/* pagemap file */
	FILE		*fp	= NULL;

	/* cleanup */
	(void)memset(path, 0, PATH_SZ);

	/* format the path variable */
	if (snprintf(path, PATH_SZ, "/proc/%d/pagemap", pid) > PATH_SZ)
		errx(4, "failed while trying to open /proc/%d/pagemap -- %s",
				pid, path);
	
	/* open the pagemap file */
	if ((fp = fopen(path, "r")) == NULL)
		errx(4, "failed while trying to open %s -- %s", path,
				strerror(errno));
	
	/* seek to the appropriate place */
	if (fseek(fp, (vaddr / psize) * sizeof(uint64_t), SEEK_CUR) == -1)
		errx(5, "failed while trying to seek in pagemap -- %s",
				strerror(errno));

	/* read the corresponding pagemap entry */
	if (fread(&pentry, sizeof(uint64_t), 1, fp) != 1) {
		if (ferror(fp))
			errx(6,
			"failed while trying to read a pagemap entry -- %s",
			strerror(errno));
		else
		errx(6,
		"unknown error while trying to read a pagemap entry -- %s",
			strerror(errno));
	}

	/* check the present bit */
	if ((pentry & PRESENT_MASK) == 0)
		warnx("%#lx is not present in physical memory", vaddr);
	else
		(void)fprintf(stdout,
				"PFN[%#lx]: %llu\n", vaddr, pentry & PFN_MASK);

	/* cleanup */
	(void)fclose(fp);
}

/*
 * getPAGEMAP
 *
 * read the pagemap of a particular process
 *
 * @argc:	number of command-line options
 * @argv:	command-line options
 * return	0: success
 * 		1: illegal option, missing argument
 * 		2: failed while trying to read the page size
 * 		3: invalid pid or virt parameter
 * 		4: failed while trying to open /proc/<pid>/pagemap
 * 		5: failed while trying to seek in /proc/<pid>/pagemap
 * 		6: failed while trying to read a pagemap entry
 */
int
main(int argc, char **argv)
{
	long		psize;		/* page size		*/
	pid_t		pid	= -1;	/* pid			*/
	unsigned long	vaddr	= 0;	/* virtual address	*/

	/* getopt stuff */
	int		opt;		/* option		*/
	int 	long_opt_indx	= 0;	/* long option index	*/

	/* long options */
	struct option long_options[] = {
		{"pid",		1, NULL, 'p'},	/* -p / --pid		*/
		{"virt",	1, NULL, 'a'},	/* -a / --virt		*/
		{"help",	0, NULL, 'h'},	/* -h / --help		*/
		{"version",	0, NULL, 'v'},	/* -v / --version	*/
		{NULL,		0, NULL, 0}};	/* terminating item	*/
	
	/* arguments parsing */
	while ((opt = getopt_long(argc, argv, ":hvp:a:", long_options,
						&long_opt_indx)) != -1) {
		switch(opt) {
			case 'p': /* -p / --pid */
				pid	= (pid_t)strtol(optarg, NULL, BASE10);
				break;
			case 'a': /* -a / --virt */
				vaddr	= (unsigned long)strtoll(optarg, NULL,
						BASE16);
				break;
			case 'h': /* help */
				help();
				goto done;
				break;	/* not reached */
			case 'v': /* version info */
				version();
				goto done;
				break;	/* not reached */
			case '?': /* illegal option */
				errx(1, "illegal option -- %s",
						(optind == 0) ?
						argv[long_opt_indx] :
						argv[optind - 1]);
				break;
			case ':': /* missing argument */
				errx(1, "option requires an argument -- %s",
						(optind == 0) ?
						argv[long_opt_indx] :
						argv[optind - 1]);
				break;
			default: /* not reached */
				break; /* make the compiler happy */
		}
	} 
	
	/* get the page size */
	if ((psize = sysconf(_SC_PAGESIZE)) == -1)
		errx(2, "failed while trying to read page size -- %s",
				strerror(errno));
	
	/* validate arguments */
	if (pid == -1)
		/* pid is missing */
		errx(3, "missing `pid' argument");
	if (pid <= 0)
		/* invalid pid value */
		errx(3, "invalid `pid' argument -- %d", pid);

	/* check if the virtual address is page-aligned */
	if ((vaddr & (psize - 1)) != 0) {
		/* verbose */
	warnx("virtual address %#lx is not page-aligned; converting to %#lx",
		vaddr, vaddr & (ULONG_MAX - (psize - 1)));
		/* fix the virtual address */
		vaddr &= ULONG_MAX - (psize - 1);
	}

	/* query pagemap */
	querypmap(pid, vaddr, psize);
	
done:	/* done; return with success */
	return EXIT_SUCCESS;
}
