/**
 * @file opd_image.c
 * Management of binary images
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 */

#include "opd_image.h"
#include "opd_printf.h"
#include "opd_sample_files.h"
#include "opd_24_stats.h"
#include "oprofiled.h"

#include "op_file.h"
#include "op_config_24.h"
#include "op_libiberty.h"
#include "op_string.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

/* maintained for statistics purpose only */
static int nr_images;

/* list of images */
#define OPD_IMAGE_HASH_SIZE 2048
static struct list_head opd_images[OPD_IMAGE_HASH_SIZE];


void opd_init_images(void)
{
	int i;
	for (i = 0; i < OPD_IMAGE_HASH_SIZE; ++i)
		list_init(&opd_images[i]);
}


int opd_get_nr_images(void)
{
	return nr_images;
}


void opd_delete_image(struct opd_image * image)
{
	verbprintf(vmisc, "Deleting image: name %s app_name %s, kernel %d, "
	           "tid %d, tgid %d ref count %u\n",
	           image->name, image->app_name, image->kernel,
	           image->tid, image->tgid, (int)image->ref_count);

	if (image->ref_count <= 0) {
		printf("image->ref_count < 0 for image: name %s app_name %s, "
		       "kernel %d, tid %d, tgid %d ref count %u\n",
		       image->name, image->app_name, image->kernel,
		       image->tid, image->tgid, image->ref_count);
		abort();
	}

	if (--image->ref_count != 0)
		return;

	if (image->name)
		free(image->name);
	if (image->app_name)
		free(image->app_name);
	list_del(&image->hash_next);
	opd_close_image_samples_files(image);
	free(image);

	nr_images--;
}


void opd_for_each_image(opd_image_cb image_cb)
{
	struct list_head * pos;
	struct list_head * pos2;
	int i;

	for (i = 0; i < OPD_IMAGE_HASH_SIZE; ++i) {
		list_for_each_safe(pos, pos2, &opd_images[i]) {
			struct opd_image * image =
				list_entry(pos, struct opd_image, hash_next);
			image_cb(image);
		}
	}
}
 

/**
 * opd_hash_image - hash an image
 * @param hash  hash of image name
 * @param tid  thread id
 * @param tgid  thread group id
 *
 * return the hash code for the passed parameters
 */
static size_t opd_hash_image(char const * name, pid_t tid, pid_t tgid)
{
	size_t hash = op_hash_string(name);
	if (separate_thread)
		hash += tid + tgid;
	return  hash % OPD_IMAGE_HASH_SIZE;
}


/**
 * opd_new_image - create an image sample file
 * @param app_name  the application name where belongs this image
 * @param name  name of the image to add
 * @param kernel  is the image a kernel/module image
 * @param tid  thread id
 * @param tgid  thread group id
 *
 * image at funtion entry is uninitialised
 * name is copied i.e. should be GC'd separately from the
 * image structure if appropriate.
 *
 * Initialise an opd_image struct for the image image
 * without opening the associated samples files. At return
 * the image is fully initialized.
 */
static struct opd_image *
opd_new_image(char const * name, char const * app_name, int kernel,
              pid_t tid, pid_t tgid)
{
	size_t hash_image;
	struct opd_image * image;

	verbprintf(vmisc, "Creating image: %s %s, kernel %d, tid %d, "
	           "tgid %d\n", name, app_name, kernel, tid, tgid);

	image = xmalloc(sizeof(struct opd_image));

	list_init(&image->hash_next);
	image->name = xstrdup(name);
	image->kernel = kernel;
	image->tid = tid;
	image->tgid = tgid;
	image->ref_count = 0;
	image->app_name = app_name ? xstrdup(app_name) : NULL;
	image->mtime = op_get_mtime(image->name);

	image->ignored = 1;
	if (separate_lib && app_name)
		image->ignored = is_image_ignored(app_name);
	if (image->ignored)
		image->ignored = is_image_ignored(name);

	memset(image->sfiles, '\0', NR_CPUS * sizeof(struct opd_24_sfile **));

	hash_image = opd_hash_image(name, tid, tgid);
	list_add(&image->hash_next, &opd_images[hash_image]);

	nr_images++;

	return image;
}


/**
 * is_same_image - check for identical image
 * @param image  image to compare
 * @param name  name of image
 * @param app_name image must belong to this application name
 * @param tid  thread id
 * @param tgid  thread group id
 *
 * on entry caller have checked than strcmp(image->name, name) == 0
 * return 0 if the couple (name, app_name) refers to same image
 */
static int is_same_image(struct opd_image const * image, char const * app_name,
                         pid_t tid, pid_t tgid)
{
	/* correctness is really important here, if we fail to recognize
	 * identical image we will open/mmap multiple time the same samples
	 * files which is not supported by the kernel, strange assertion
	 * failure in libfd is a typical symptom of that */

	if (separate_thread) {
		if (image->tid != tid || image->tgid != tgid)
			return 1;
	}

	/* if !separate_lib, the comparison made by caller is enough */
	if (!separate_lib)
		return 0;

	if (image->app_name == NULL && app_name == NULL)
		return 0;

	if (image->app_name != NULL && app_name != NULL &&
	    !strcmp(image->app_name, app_name))
		return 0;

	/* /proc parsed image come with a non null app_name but notification
	 * for application itself come with a null app_name, in this case
	 * the test above fail so check for this case. */
	if (image->app_name && !app_name && !strcmp(image->app_name, image->name))
		return 0;

	return 1;
}


/**
 * opd_find_image - find an image
 * @param name  name of image to find
 * @param hash  hash of image to find
 * @param app_name  the application name where belongs this image
 * @param tid  thread id
 * @param tgid  thread group id
 *
 * Returns the image pointer for the file specified by name, or %NULL.
 */
static struct opd_image * opd_find_image(char const * name, 
                                char const * app_name, pid_t tid, pid_t tgid)
{
	/* suppress uninitialized use warning */
	struct opd_image * image = 0;
	struct list_head * pos;
	size_t bucket;

	opd_24_stats[OPD_IMAGE_HASH_ACCESS]++;
	bucket = opd_hash_image(name, tid, tgid);
	list_for_each(pos, &opd_images[bucket]) {
		opd_24_stats[OPD_IMAGE_HASH_DEPTH]++;
		image = list_entry(pos, struct opd_image, hash_next);

		if (!strcmp(image->name, name)) {
			if (!is_same_image(image, app_name, tid, tgid))
				break;
		}
	}

	if (pos == &opd_images[bucket])
		return NULL;

	/* The app_name field is always valid */
	return image;
}

 
struct opd_image * opd_get_image(char const * name, char const * app_name,
                                 int kernel, pid_t tid, pid_t tgid)
{
	struct opd_image * image;
	if ((image = opd_find_image(name, app_name, tid, tgid)) == NULL)
		image = opd_new_image(name, app_name, kernel, tid, tgid);

	return image;
}


struct opd_image * opd_get_kernel_image(char const * name,
                               char const * app_name, pid_t tid, pid_t tgid)
{
	return opd_get_image(name, app_name, 1, tid, tgid);
}