/*
 * (c) Copyright IBM Corp. 2005 All Rights Reserved
 *
 * Physical Memory Information Module
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * at your option any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/**
 * @file main.c
 *
 * @brief Main module for physical memory analyzer.
 *
 * This module provides the main() and various utility and support
 * functions for the physical memory analyzer. This program takes
 * as input the data from the kernel module that is also part of
 * this toolset. It provides various analysis options to help
 * determine how physical memory is being utilized.
 *
 * There are also various output options for human readable or
 * comma separated value (CSV) suitable for import into spreadsheets.
 *
 * @author International Business Machines
 * @author Paul Movall <movall@us.ibm.com>
 *
 * @version Current Version: 1.0
 *
 * @date Current Date: 01/2005
 *
 * @version 0.1, 11/2003: File created by Paul Movall <movall@us.ibm.com>
 * @version 1.0, 01/2005: Miscellaneous cleanup for publish
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/param.h>

/**
 * @def MAIN_C Used to signify that this is the main module in the local
 * header file. This is used internally to correctly generate global
 * variables.
 */
#define MAIN_C
#include "td.h"

/* ========================================================================== */
/**
 * @brief Global handle for the input argument parser.
 */
extern char *optarg;

/**
 * @brief Global file handle used as the input file by the lexical parser.
 */
extern FILE *yyin;

/**
 * @brief The lexical parser function.
 */
extern void yylex(void);

/* documented in parsers.c */
extern void parse_data(FILE *outf, int i_sel);

/* ========================================================================== */
/* documented in td.h */
unsigned long xtoi(char *str) {
	unsigned long long sum = 0;
	int indx = 0;
	int done = FALSE;

	do {
		sum = sum * 0x10;

		switch(str[indx]) {
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			sum += (str[indx] - '0');
			break;
		case 'a':
		case 'b':
		case 'c':
		case 'd':
		case 'e':
		case 'f':
			sum += (str[indx] - 'a' + 10);
			break;
		case 'A':
		case 'B':
		case 'C':
		case 'D':
		case 'E':
		case 'F':
			sum += (str[indx] - 'A' + 10);
			break;
		default:
			done = TRUE;
			break;
		}
		indx++;
	} while (!done);

	return((unsigned long)(sum / 0x10));
}

/* documented in td.h */
int find_task(task_t *this_task) {
	int found = FALSE;
	task_t *cur_task;

	for (cur_task = head_task.head;
	     cur_task != NULL && !found;
	     cur_task = cur_task->next) {
		if (cur_task->mm == this_task->mm) {
			found = TRUE;
		}
	}

	return(found);
}

/* documented in td.h */
void add_task(task_t *this_task) {
	if (head_task.tail == NULL) {
		head_task.head = this_task;
		head_task.tail = this_task;
	} else {
		head_task.tail->next = this_task;
		head_task.tail = this_task;
	}
}

/* documented in td.h */
void add_vma(task_t *cur_task, vma_t *cur_vma) {
	if (cur_task->vma_tail == NULL) {
		cur_task->vma_tail = cur_vma;
		cur_task->vma_head = cur_vma;
	} else {
		cur_task->vma_tail->next = cur_vma;
		cur_vma->prev = cur_task->vma_tail;
		cur_task->vma_tail = cur_vma;
	}
}

/* documented in td.h */
void print_usage() {
	/* i:o:r:d:tv */
}

/**
 * @brief Output the VMA summary information for an added or deleted VMA.
 *
 * @param cur_vma The VMA to format.
 * @param outf The output file.
 *
 * @return None.
 */
void vma_summary(vma_t *cur_vma, FILE *outf) {
	pte_t *cur_pte = cur_vma->pte_info;
	char *vf;

	if (!summary) {
		if (terse) {
			fprintf(outf, "%s, ", cur_vma->name);
			vf = FORMAT_VMA_DETAIL_TERSE;
		} else {
			vf = FORMAT_VMA_DETAIL_VERBOSE;
		}

		fprintf(outf,
			vf,
			cur_vma->vlen / PAGE_SIZE,
			cur_pte->single_use_count,
			cur_pte->shared_count);

		print_vma_flags(cur_vma, outf);

		fprintf(outf, ", %#010lX, %#010lX, ",
			cur_vma->start, cur_vma->end);

		if (!terse) {
			fprintf(outf, "name: %s\n",
				cur_vma->name);
		}

		print_vma_map(outf, cur_vma);
	}
}


/* documented in td.h */
void task_summary(task_t *cur_task, FILE *outf, int vma_detail, FILE *postf) {
	vma_t *cur_vma;
	pte_t *cur_pte;
	pte_t virt_pte;
	int rw_count;
	int rx_count;
	int stack_count;
	int task_text;
	int task_data;
	int heap_count;
	int rx_single;
	int rx_multi;
	int lib_bss;
	int lib_data;
	int vrw_count;
	int vrx_count;
	int vstack_count;
	int vtask_text;
	int vtask_data;
	int vheap_count;
	int vrx_single;
	int vrx_multi;
	int vlib_bss;
	int vlib_data;
	char *f1, *f2, *f3, *f4, *vf;

	rx_count = rw_count = task_data = task_text = stack_count =
	  heap_count = rx_single = rx_multi = lib_bss = lib_data = 0;
	vrx_count = vrw_count = vtask_data = vtask_text = vstack_count =
	  vheap_count = vrx_single = vrx_multi = vlib_bss = vlib_data = 0;

	if (vma_detail) {
		fprintf(outf,
			"%s:\n",
			cur_task->name);
	}

	for (cur_vma = cur_task->vma_head;
	     cur_vma != NULL;
	     cur_vma = cur_vma->next) {

		virt_pte.user_space_count = cur_vma->vlen / PAGE_SIZE;
		if ((cur_vma->vma_flags & VMA_FLAGS_MASK_RWX) !=
		    VMA_FLAGS_RWX) {
			/* for r.x pages, assume shared */
			virt_pte.shared_count =
			  virt_pte.user_space_count;
			virt_pte.single_use_count = 0;
		} else {
			virt_pte.shared_count = 0;
			virt_pte.single_use_count =
			  virt_pte.user_space_count;
		}
		cur_pte = cur_vma->pte_info;

		if (vma_detail) {
			vma_summary(cur_vma, outf);
		}

		switch (cur_vma->vma_flags & VMA_FLAGS_MASK_RWX) {
		case (VMA_FLAGS_RD | VMA_FLAGS_EXEC):
			rx_count += cur_pte->user_space_count;
			vrx_count += virt_pte.user_space_count;

			if ((cur_vma->vma_flags &
			     VMA_FLAGS_EXECUTABLE) ==
			    VMA_FLAGS_EXECUTABLE) {
				task_text += cur_pte->user_space_count;
				vtask_text += virt_pte.user_space_count;
			} else {
				rx_single += cur_pte->single_use_count;
				rx_multi += cur_pte->shared_count;
				vrx_single += virt_pte.single_use_count;
				vrx_multi += virt_pte.shared_count;
			}
			break;
		case VMA_FLAGS_RWX:
		case (VMA_FLAGS_RD | VMA_FLAGS_WR):
			rw_count += cur_pte->user_space_count;
			vrw_count += virt_pte.user_space_count;

			if ((cur_vma->vma_flags & VMA_FLAGS_STACK) != 0)
			{
				stack_count +=
				  cur_pte->user_space_count;
				vstack_count +=
				  virt_pte.user_space_count;
			} else if ((cur_vma->vma_flags &
				    VMA_FLAGS_EXECUTABLE) ==
				   VMA_FLAGS_EXECUTABLE) {
				if ((cur_vma->vma_flags &
				     VMA_FLAGS_BSS) ==
				    VMA_FLAGS_BSS) {
					heap_count +=
					  cur_pte->user_space_count;
					vheap_count +=
					  virt_pte.user_space_count;
				} else {
					task_data +=
					  cur_pte->user_space_count;
					vtask_data +=
					  virt_pte.user_space_count;
				}
			} else if (!strcmp(cur_vma->name, "NULL")) {
				if ((cur_vma->vma_flags & VMA_FLAGS_BSS)
				    == VMA_FLAGS_BSS) {
						/*
						 * this is a lib
						 * .bss area
						 */
					lib_bss +=
					  cur_pte->user_space_count;
					vlib_bss +=
					  virt_pte.user_space_count;
				} else {
						/*
						 * anon memory that's not
						 * lib .bss so must be heap
						 */
					heap_count +=
					  cur_pte->user_space_count;
					vheap_count +=
					  virt_pte.user_space_count;
				}
			} else {
				lib_data += cur_pte->user_space_count;
				vlib_data += virt_pte.user_space_count;
			}
			break;
		case 0:
				/* no pages used so just skip it */
			break;
		default:
				/* hmm, don't know so print error and skip */
			fprintf(stderr,
				"%s: error parsing vma flags [%08lX] "
				"for task %s...skipping...\n",
				"mem_analysis",
				cur_vma->vma_flags,
				cur_task->name);
			break;
		}
	}

	if (terse) {
		f1 = FORMAT_TASK_DETAIL_TERSE_1;
		f2 = FORMAT_TASK_DETAIL_TERSE_2;
		f3 = FORMAT_TASK_DETAIL_TERSE_3;
		f4 = FORMAT_TASK_DETAIL_TERSE_4;
	} else {
		f1 = FORMAT_TASK_DETAIL_VERBOSE_1;
		f2 = FORMAT_TASK_DETAIL_VERBOSE_2;
		f3 = FORMAT_TASK_DETAIL_VERBOSE_3;
		f4 = FORMAT_TASK_DETAIL_VERBOSE_4;
	}

	if (virtual) {
		fprintf(outf,
			f1,
			cur_task->name,
			vrw_count,
			vrx_count,
			vrw_count + vrx_count);
		fprintf(outf,
			f2,
			cur_task->name,
			vstack_count,
			vtask_data,
			vtask_text,
			vheap_count);
		fprintf(outf,
			f3,
			vlib_bss,
			vlib_data,
			vrx_single + vrx_multi);
		fprintf(outf,
			f4,
			vrx_single,
			vrx_multi);
	} else {
		fprintf(outf,
			f1,
			cur_task->name,
			rw_count,
			rx_count,
			rw_count + rx_count);
		fprintf(outf,
			f2,
			cur_task->name,
			stack_count,
			task_data,
			task_text,
			heap_count);
		fprintf(outf,
			f3,
			lib_bss,
			lib_data,
			rx_single + rx_multi);
		fprintf(outf,
			f4,
			rx_single,
			rx_multi);
	}

	if ( (post_parm) && (postf != NULL) ) {
		fprintf(postf,
			"(%s) 0 ypos FH FHdelta add taskname\n",
			cur_task->name);

		fprintf(postf, "%d %d %d %d %d %d %d %f taskpgs\n",
			stack_count,
			task_data,
			task_text,
			heap_count,
			lib_bss,
			lib_data,
			rx_single + rx_multi,
			virtual ? 0.6 : 0.35);

		if (virtual) {
			fprintf(postf, "%d %d %d %d %d %d %d %f taskpgs\n",
				vstack_count,
				vtask_data,
				vtask_text,
				vheap_count,
				vlib_bss,
				vlib_data,
				vrx_single + vrx_multi,
				0.1);
		}

		fprintf(postf, "/ypos ypos FH FHdelta add sub store\n\n");
	}


}

void print_vma_flags(vma_t *cur_vma, FILE *outf) {
	fprintf(outf, "%c",
		( (cur_vma->vma_flags &
		   VMA_FLAGS_STACK) ?
		  'S' : '.'));
	fprintf(outf, "%c",
		( (cur_vma->vma_flags &
		   VMA_FLAGS_EXECUTABLE) ?
		  'X' : '.'));
	fprintf(outf, "%c",
		( (cur_vma->vma_flags &
		   VMA_FLAGS_RD) ?
		  'r' : '.'));
	fprintf(outf, "%c",
		( (cur_vma->vma_flags &
		   VMA_FLAGS_WR) ?
		  'w' : '.'));
	fprintf(outf, "%c",
		( (cur_vma->vma_flags &
		   VMA_FLAGS_EXEC) ?
		  'x' : '.'));
	fprintf(outf, "%c",
		( (cur_vma->vma_flags &
		   VMA_FLAGS_BSS) ?
		  'B' : '.'));
}

/**
 * @brief The standard main() function.
 *
 * Performs input parameter parsing, global data initialization,
 * opens various files, and calls into the data input parser and
 * the data format parser.
 *
 * @param argc The input argument count.
 * @param argv The input argument list.
 *
 * @retval 0 The program completed successfully.
 */
int main(int argc, char *argv[]) {
	int i = 0;
	int indx;
	int option;
	FILE *outfp;
	int out_parm = FALSE;
	int in_sel = -1;
	task_list_head_t temp_head;

	terse = FALSE;
	post_parm = FALSE;
	pstack_filter = FALSE;
	pstack_size = 16;

	strcpy(infile, "");
	strcpy(outfile, "");
	strcpy(post_file, "");

	while ((option = getopt(argc, argv,"i:o:r:p:f:tvs")) != -1)
	{
		switch (option) {
		case 'i':
			strcpy(infile, optarg);
			break;

		case 'o':
			strcpy(outfile, optarg);
			out_parm = TRUE;
			break;

		case 'p':
			post_parm = TRUE;
			strcpy(post_file, optarg);
			break;

		case 'r':
			in_sel = atoi(optarg);
			break;

		case 'f':
			pstack_filter = TRUE;
			pstack_size = atoi(optarg);
			break;

		case 't':
			terse = TRUE;
			break;

		case 'v':
			virtual = TRUE;
			break;

		case 's':
			summary = TRUE;
			break;

		default:
			print_usage();
			break;
		}
	}

	yyin = fopen(infile, "r");
	if (yyin == NULL) {
		fprintf(stderr,
			"Error opening input file [%s]. "
			"Using stdin instead.\n",
			infile);
		yyin = stdin;
		strcpy(infile, "stdin");
	}

	if (out_parm) {
		outfp = fopen(outfile, "w");
		if (outfp == NULL) {
			fprintf(stderr,
				"Error opening output file [%s]. "
				"Using stdout instead.\n",
				outfile);
			outfp = stdout;
			strcpy(outfile, "stdout");
		}
	} else {
		outfp = stdout;
		strcpy(outfile, "stdout");
	}

	/* set task_valid to true to force it to not try to free */
	task_valid = TRUE;
	head_task.head = NULL;
	head_task.tail = NULL;

	yylex();

	if (yyin != stdin) {
		/* did not use stdin, so can close it */
		fclose(yyin);
	}

	parse_data(outfp, in_sel);

	fclose(outfp);

	return(0);
}

#undef MAIN_C
