/**
 * @file opd_mapping.c
 * Management of process mappings
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 */

#include "opd_mapping.h"
#include "opd_proc.h"
#include "opd_image.h"
#include "opd_printf.h"

#include "op_interface.h"
#include "op_config_24.h"
#include "op_libiberty.h"

#include <sys/mman.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/* hash map device mmap */
static struct op_hash_index * hashmap;
/* already seen mapping name */
static char const * hash_name[OP_HASH_MAP_NR];


void opd_cleanup_hash_name(void)
{
	int i;
	for (i = 0; i < OP_HASH_MAP_NR; ++i)
		free((char *)hash_name[i]);
	
}


void opd_init_hash_map(void)
{
	extern fd_t hashmapdevfd;

	hashmap = mmap(0, OP_HASH_MAP_SIZE, PROT_READ, MAP_SHARED, hashmapdevfd, 0);
	if ((long)hashmap == -1) {
		perror("oprofiled: couldn't mmap hash map");
		exit(EXIT_FAILURE);
	}

}


void opd_kill_maps(struct opd_proc * proc)
{
	struct list_head * pos, * pos2;

	list_for_each_safe(pos, pos2, &proc->maps) {
		struct opd_map * map = list_entry(pos, struct opd_map, next);
		list_del(pos);
		opd_delete_image(map->image);
		free(map);
	}
}


void opd_add_mapping(struct opd_proc * proc, struct opd_image * image,
		unsigned long start, unsigned long offset, unsigned long end)
{
	struct opd_map * map;

	verbprintf(vmisc, "Adding mapping for process %d: 0x%.8lx-0x%.8lx, off 0x%.8lx, \"%s\"\n",
		proc->tid, start, end, offset, image->name);

	map = malloc(sizeof(struct opd_map));

	/* first map is the primary image */
	if (list_empty(&proc->maps)) {
		if (proc->name)
			free((char *)proc->name);
		proc->name = xstrdup(image->name);
	}

	image->ref_count++;

	map->image = image;
	map->start = start;
	map->offset = offset;
	map->end = end;
	list_add_tail(&map->next, &proc->maps);
}


/**
 * get_from_pool - retrieve string from hash map pool
 * @param ind index into pool
 */
inline static char * get_from_pool(uint ind)
{
	return ((char *)(hashmap + OP_HASH_MAP_NR) + ind);
}


/**
 * opg_get_hash_name - find a mapping name from a hash
 * @param hash hash value for this name
 */
static char const * opd_get_hash_name(int hash)
{
	char file[PATH_MAX];
	char * c = &file[PATH_MAX-1];
	int orighash = hash;

	if (hash_name[hash])
		return hash_name[hash];

	*c = '\0';
	while (hash) {
		char * name = get_from_pool(hashmap[hash].name);

		if (strlen(name) + 1 + strlen(c) >= PATH_MAX) {
			fprintf(stderr, "String \"%s\" too large.\n", c);
			exit(EXIT_FAILURE);
		}

		c -= strlen(name) + 1;
		*c = '/';
		strncpy(c + 1, name, strlen(name));

		/* move onto parent */
		hash = hashmap[hash].parent;
	}

	return hash_name[orighash] = xstrdup(c);
}


void opd_handle_mapping(struct op_note const * note)
{
	struct opd_proc * proc;
	struct opd_image * image;
	int hash;
	char const * name;

	proc = opd_get_proc(note->pid, note->tgid);

	if (!proc) {
		verbprintf(vmisc, "Told about mapping for non-existent process %u.\n", note->pid);
		proc = opd_new_proc(note->pid, note->tgid);
	}

	hash = note->hash;

	if (hash == -1) {
		/* possibly deleted file */
		return;
	}

	if (hash < 0 || hash >= OP_HASH_MAP_NR) {
		fprintf(stderr, "hash value %u out of range.\n", hash);
		return;
	}

	name = opd_get_hash_name(hash);
	image = opd_get_image(name, proc->name, 0, note->pid, note->tgid);

	opd_add_mapping(proc, image, note->addr, note->offset,
	                note->addr + note->len);
}