/**
 * @file opd_cookie.c
 * cookie -> name cache
 *
 * @remark Copyright 2002, 2005 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 */

#include "opd_cookie.h"
#include "oprofiled.h"
#include "op_list.h"
#include "op_libiberty.h"

#include <sys/syscall.h>
#include <unistd.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#ifndef __NR_lookup_dcookie
#if defined(__i386__)
#define __NR_lookup_dcookie 253
#elif defined(__x86_64__)
#define __NR_lookup_dcookie 212
#elif defined(__powerpc__)
#define __NR_lookup_dcookie 235
#elif defined(__alpha__)
#define __NR_lookup_dcookie 406
#elif defined(__hppa__)
#define __NR_lookup_dcookie 223
#elif defined(__ia64__)
#define __NR_lookup_dcookie 1237
#elif defined(__sparc__)
/* untested */
#define __NR_lookup_dcookie 208
#elif defined(__s390__) || defined (__s390x__)
#define __NR_lookup_dcookie 110
#elif defined(__arm__)
#define __NR_lookup_dcookie (__NR_SYSCALL_BASE+249)
#elif defined(__mips__)
#include <sgidefs.h>
/* O32 */
#if _MIPS_SIM == _MIPS_SIM_ABI32
#define __NR_lookup_dcookie 4247
/* N64 */
#elif _MIPS_SIM == _MIPS_SIM_ABI64
#define __NR_lookup_dcookie 5206
/* N32 */
#elif _MIPS_SIM == _MIPS_SIM_NABI32
#define __NR_lookup_dcookie 6206
#else
#error Unknown MIPS ABI: Dunno __NR_lookup_dcookie
#endif
#else
#error Please define __NR_lookup_dcookie for your architecture
#endif
#endif /* __NR_lookup_dcookie */

#if (defined(__powerpc__) && !defined(__powerpc64__)) || defined(__hppa__)\
	|| (defined(__s390__) && !defined(__s390x__)) \
	|| (defined(__mips__) && (_MIPS_SIM == _MIPS_SIM_ABI32) \
	    && defined(__MIPSEB__)) \
        || (defined(__arm__) && defined(__ARM_EABI__) \
            && defined(__ARMEB__))
static inline int lookup_dcookie(cookie_t cookie, char * buf, size_t size)
{
	return syscall(__NR_lookup_dcookie, (unsigned long)(cookie >> 32),
		       (unsigned long)(cookie & 0xffffffff), buf, size);
}
#elif (defined(__mips__) && (_MIPS_SIM == _MIPS_SIM_ABI32)) \
	|| (defined(__arm__) && defined(__ARM_EABI__))
static inline int lookup_dcookie(cookie_t cookie, char * buf, size_t size)
{
	return syscall(__NR_lookup_dcookie,
		       (unsigned long)(cookie & 0xffffffff),
		       (unsigned long)(cookie >> 32), buf, size);
}
#else
static inline int lookup_dcookie(cookie_t cookie, char * buf, size_t size)
{
	return syscall(__NR_lookup_dcookie, cookie, buf, size);
}
#endif


struct cookie_entry {
	cookie_t value;
	char * name;
	int ignored;
	struct list_head list;
};


#define HASH_SIZE 512
#define HASH_BITS (HASH_SIZE - 1)

static struct list_head hashes[HASH_SIZE];

static struct cookie_entry * create_cookie(cookie_t cookie)
{
	int err;
	struct cookie_entry * entry = xmalloc(sizeof(struct cookie_entry));

	entry->value = cookie;
	entry->name = xmalloc(PATH_MAX + 1);

	err = lookup_dcookie(cookie, entry->name, PATH_MAX);

	if (err < 0) {
		fprintf(stderr, "Lookup of cookie %llx failed, errno=%d\n",
		       cookie, errno); 
		free(entry->name);
		entry->name = NULL;
		entry->ignored = 0;
	} else {
		entry->ignored = is_image_ignored(entry->name);
	}

	return entry;
}


/* Cookie monster want cookie! */
static unsigned long hash_cookie(cookie_t cookie)
{
	return (cookie >> DCOOKIE_SHIFT) & (HASH_SIZE - 1);
}
 

char const * find_cookie(cookie_t cookie)
{
	unsigned long hash = hash_cookie(cookie);
	struct list_head * pos;
	struct cookie_entry * entry;

	if (cookie == INVALID_COOKIE || cookie == NO_COOKIE)
		return NULL;

	list_for_each(pos, &hashes[hash]) {
		entry = list_entry(pos, struct cookie_entry, list);
		if (entry->value == cookie)
			goto out;
	}

	/* not sure this can ever happen due to is_cookie_ignored */
	entry = create_cookie(cookie);
	list_add(&entry->list, &hashes[hash]);
out:
	return entry->name;
}


int is_cookie_ignored(cookie_t cookie)
{
	unsigned long hash = hash_cookie(cookie);
	struct list_head * pos;
	struct cookie_entry * entry;

	if (cookie == INVALID_COOKIE || cookie == NO_COOKIE)
		return 1;

	list_for_each(pos, &hashes[hash]) {
		entry = list_entry(pos, struct cookie_entry, list);
		if (entry->value == cookie)
			goto out;
	}

	entry = create_cookie(cookie);
	list_add(&entry->list, &hashes[hash]);
out:
	return entry->ignored;
}


char const * verbose_cookie(cookie_t cookie)
{
	unsigned long hash = hash_cookie(cookie);
	struct list_head * pos;
	struct cookie_entry * entry;

	if (cookie == INVALID_COOKIE)
		return "invalid";

	if (cookie == NO_COOKIE)
		return "anonymous";

	list_for_each(pos, &hashes[hash]) {
		entry = list_entry(pos, struct cookie_entry, list);
		if (entry->value == cookie) {
			if (!entry->name)
				return "failed lookup";
			return entry->name;
		}
	}

	return "not hashed";
}


void cookie_init(void)
{
	size_t i;

	for (i = 0; i < HASH_SIZE; ++i)
		list_init(&hashes[i]);
}