/**
 * @file daemon/oprofiled.c
 * Initialisation and setup
 *
 * @remark Copyright 2002, 2003 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 * Modified by Aravind Menon for Xen
 * These modifications are:
 * Copyright (C) 2005 Hewlett-Packard Co.
 */

#include "config.h"
 
#include "oprofiled.h"
#include "opd_printf.h"
#include "opd_events.h"
#include "opd_extended.h"

#include "op_config.h"
#include "op_version.h"
#include "op_hw_config.h"
#include "op_libiberty.h"
#include "op_file.h"
#include "op_abi.h"
#include "op_string.h"
#include "op_cpu_type.h"
#include "op_popt.h"
#include "op_lockfile.h"
#include "op_list.h"
#include "op_fileio.h"

#include <sys/types.h>
#include <sys/resource.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <dirent.h>
#include <limits.h>

sig_atomic_t signal_alarm;
sig_atomic_t signal_hup;
sig_atomic_t signal_term;
sig_atomic_t signal_child;
sig_atomic_t signal_usr1;
sig_atomic_t signal_usr2;

uint op_nr_counters;
op_cpu cpu_type;
int no_event_ok;
int vsfile;
int vsamples;
int varcs;
int vmodule;
int vmisc;
int vext;
int separate_lib;
int separate_kernel;
int separate_thread;
int separate_cpu;
int no_vmlinux;
char * vmlinux;
char * kernel_range;
char * session_dir;
int no_xen;
char * xenimage;
char * xen_range;
static char * verbose;
static char * binary_name_filter;
static char * events;
static char * ext_feature;
static int showvers;
static struct oprofiled_ops * opd_ops;
extern struct oprofiled_ops opd_24_ops;
extern struct oprofiled_ops opd_26_ops;

#define OPD_IMAGE_FILTER_HASH_SIZE 32
static struct list_head images_filter[OPD_IMAGE_FILTER_HASH_SIZE];

static struct poptOption options[] = {
	{ "session-dir", 0, POPT_ARG_STRING, &session_dir, 0, "place sample database in dir instead of default location", "/var/lib/oprofile", },
	{ "kernel-range", 'r', POPT_ARG_STRING, &kernel_range, 0, "Kernel VMA range", "start-end", },
	{ "vmlinux", 'k', POPT_ARG_STRING, &vmlinux, 0, "vmlinux kernel image", "file", },
	{ "no-vmlinux", 0, POPT_ARG_NONE, &no_vmlinux, 0, "vmlinux kernel image file not available", NULL, },
	{ "xen-range", 0, POPT_ARG_STRING, &xen_range, 0, "Xen VMA range", "start-end", },
	{ "xen-image", 0, POPT_ARG_STRING, &xenimage, 0, "Xen image", "file", },
	{ "image", 0, POPT_ARG_STRING, &binary_name_filter, 0, "image name filter", "profile these comma separated image" },
	{ "separate-lib", 0, POPT_ARG_INT, &separate_lib, 0, "separate library samples for each distinct application", "[0|1]", },
	{ "separate-kernel", 0, POPT_ARG_INT, &separate_kernel, 0, "separate kernel samples for each distinct application", "[0|1]", },
	{ "separate-thread", 0, POPT_ARG_INT, &separate_thread, 0, "thread-profiling mode", "[0|1]" },
	{ "separate-cpu", 0, POPT_ARG_INT, &separate_cpu, 0, "separate samples for each CPU", "[0|1]" },
	{ "events", 'e', POPT_ARG_STRING, &events, 0, "events list", "[events]" },
	{ "version", 'v', POPT_ARG_NONE, &showvers, 0, "show version", NULL, },
	{ "verbose", 'V', POPT_ARG_STRING, &verbose, 0, "be verbose in log file", "all,sfile,arcs,samples,module,misc", },
	{ "ext-feature", 'x', POPT_ARG_STRING, &ext_feature, 1, "enable extended feature", "<extended-feature-name>:[args]", },
	POPT_AUTOHELP
	{ NULL, 0, 0, NULL, 0, NULL, NULL, },
};
 

void opd_open_logfile(void)
{
	if (open(op_log_file, O_WRONLY|O_CREAT|O_NOCTTY|O_APPEND, 0644) == -1) {
		perror("oprofiled: couldn't re-open stdout: ");
		exit(EXIT_FAILURE);
	}

	if (dup2(1, 2) == -1) {
		perror("oprofiled: couldn't dup stdout to stderr: ");
		exit(EXIT_FAILURE);
	}
}
 

/**
 * opd_fork - fork and return as child
 *
 * fork() and exit the parent with _exit().
 * Failure is fatal.
 */
static void opd_fork(void)
{
	switch (fork()) {
		case -1:
			perror("oprofiled: fork() failed: ");
			exit(EXIT_FAILURE);
			break;
		case 0:
			break;
		default:
			/* parent */
			_exit(EXIT_SUCCESS);
			break;
	}
}

 
static void opd_go_daemon(void)
{
	opd_fork();

	if (chdir(op_session_dir)) {
		fprintf(stderr, "oprofiled: opd_go_daemon: couldn't chdir to %s: %s",
			op_session_dir, strerror(errno));
		exit(EXIT_FAILURE);
	}

	if (setsid() < 0) {
		perror("oprofiled: opd_go_daemon: couldn't setsid: ");
		exit(EXIT_FAILURE);
	}

	opd_fork();
}


static void opd_write_abi(void)
{
	char * cbuf;
 
	cbuf = xmalloc(strlen(op_session_dir) + 5);
	strcpy(cbuf, op_session_dir);
	strcat(cbuf, "/abi");
	op_write_abi_to_file(cbuf);
	free(cbuf);
}


/**
 * opd_alarm - sync files and report stats
 */
static void opd_alarm(int val __attribute__((unused)))
{
	signal_alarm = 1;
}
 

/* re-open logfile for logrotate */
static void opd_sighup(int val __attribute__((unused)))
{
	signal_hup = 1;
}


static void opd_sigterm(int val __attribute__((unused)))
{
	signal_term = 1;
}

static void opd_sigchild(int val __attribute__((unused)))
{
	signal_child = 1;
}
 

static void opd_sigusr1(int val __attribute__((unused)))
{
	signal_usr1 = 1;
}

 
static void opd_sigusr2(int val __attribute__((unused)))
{
	signal_usr2 = 1;
}


static void opd_setup_signals(void)
{
	struct sigaction act;
 
	act.sa_handler = opd_alarm;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);

	if (sigaction(SIGALRM, &act, NULL)) {
		perror("oprofiled: install of SIGALRM handler failed: ");
		exit(EXIT_FAILURE);
	}

	act.sa_handler = opd_sighup;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask, SIGALRM);

	if (sigaction(SIGHUP, &act, NULL)) {
		perror("oprofiled: install of SIGHUP handler failed: ");
		exit(EXIT_FAILURE);
	}

	act.sa_handler = opd_sigterm;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask, SIGTERM);

	if (sigaction(SIGTERM, &act, NULL)) {
		perror("oprofiled: install of SIGTERM handler failed: ");
		exit(EXIT_FAILURE);
	}

	act.sa_handler = opd_sigchild;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask, SIGCHLD);

	if (sigaction(SIGCHLD, &act, NULL)) {
		perror("oprofiled: install of SIGCHLD handler failed: ");
		exit(EXIT_FAILURE);
	}

	act.sa_handler = opd_sigusr1;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask, SIGTERM);

	if (sigaction(SIGUSR1, &act, NULL)) {
		perror("oprofiled: install of SIGUSR1 handler failed: ");
		exit(EXIT_FAILURE);
	}

	act.sa_handler = opd_sigusr2;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask, SIGTERM);

	if (sigaction(SIGUSR2, &act, NULL)) {
		perror("oprofiled: install of SIGUSR2 handler failed: ");
		exit(EXIT_FAILURE);
	}
}


struct opd_hashed_name {
	char * name;
	struct list_head next;
};


static void add_image_filter(char const * name)
{
	size_t hash;
	struct opd_hashed_name * elt = xmalloc(sizeof(struct opd_hashed_name));
	elt->name = xmalloc(PATH_MAX);
	if (!realpath(name, elt->name)) {
		free(elt->name);
		free(elt);
		return;
	}
	hash = op_hash_string(elt->name);
	verbprintf(vmisc, "Adding to image filter: \"%s\"\n", elt->name);
	list_add(&elt->next, &images_filter[hash % OPD_IMAGE_FILTER_HASH_SIZE]);
}


static void opd_parse_image_filter(void)
{
	size_t i;
	char const * last = binary_name_filter;
	char const * cur = binary_name_filter;

	if (!binary_name_filter)
		return;

	for (i = 0; i < OPD_IMAGE_FILTER_HASH_SIZE; ++i)
		list_init(&images_filter[i]);

	while ((cur = strchr(last, ',')) != NULL) {
		char * tmp = op_xstrndup(last, cur - last);
		add_image_filter(tmp);
		free(tmp);
		last = cur + 1;
	}
	add_image_filter(last);
}


int is_image_ignored(char const * name)
{
	size_t hash;
	struct list_head * pos;

	if (!binary_name_filter)
		return 0;
	
	hash = op_hash_string(name);

	list_for_each(pos, &images_filter[hash % OPD_IMAGE_FILTER_HASH_SIZE]) {
		struct opd_hashed_name * hashed_name =
			list_entry(pos, struct opd_hashed_name, next);
		if (!strcmp(hashed_name->name, name))
			return 0;
	}

	return 1;
}


/** return the int in the given oprofilefs file */
int opd_read_fs_int(char const * path, char const * name, int fatal)
{
	char filename[PATH_MAX + 1];
	snprintf(filename, PATH_MAX, "%s/%s", path, name);
	return op_read_int_from_file(filename, fatal);
}


static void opd_handle_verbose_option(char const * name)
{
	if (!strcmp(name, "all")) {
		vsfile = 1;
		vsamples = 1;
		varcs = 1;
		vmodule = 1;
		vmisc = 1;
		vext= 1;
	} else if (!strcmp(name, "sfile")) {
		vsfile = 1;
	} else if (!strcmp(name, "arcs")) {
		varcs = 1;
	} else if (!strcmp(name, "samples")) {
		vsamples = 1;
	} else if (!strcmp(name, "module")) {
		vmodule = 1;
	} else if (!strcmp(name, "misc")) {
		vmisc = 1;
	} else if (!strcmp(name, "ext")) {
		vext= 1;
	} else {
		fprintf(stderr, "unknown verbose options\n");
		exit(EXIT_FAILURE);
	}
}

static void opd_parse_verbose(void)
{
	char const * last = verbose;
	char const * cur = verbose;

	if (!verbose)
		return;

	while ((cur = strchr(last, ',')) != NULL) {
		char * tmp = op_xstrndup(last, cur - last);
		opd_handle_verbose_option(tmp);
		free(tmp);
		last = cur + 1;
	}
	opd_handle_verbose_option(last);
}


static void opd_options(int argc, char const * argv[])
{
	poptContext optcon;
	char * tmp;

	optcon = op_poptGetContext(NULL, argc, argv, options, 0);

	if (showvers)
		show_version(argv[0]);

	opd_parse_verbose();

	if (separate_kernel)
		separate_lib = 1;

	cpu_type = op_get_cpu_type();
	op_nr_counters = op_get_nr_counters(cpu_type);

	if (!no_vmlinux) {
		if (!vmlinux || !strcmp("", vmlinux)) {
			fprintf(stderr, "oprofiled: no vmlinux specified.\n");
			poptPrintHelp(optcon, stderr, 0);
			exit(EXIT_FAILURE);
		}

		/* canonicalise vmlinux filename. fix #637805 */
		tmp = xmalloc(PATH_MAX);
		if (realpath(vmlinux, tmp))
			vmlinux = tmp;
		else
			free(tmp);

		if (!kernel_range || !strcmp("", kernel_range)) {
			fprintf(stderr, "oprofiled: no kernel VMA range specified.\n");
			poptPrintHelp(optcon, stderr, 0);
			exit(EXIT_FAILURE);
		}
	}

	if(opd_ext_initialize(ext_feature) != EXIT_SUCCESS)
		exit(EXIT_FAILURE);

	if (events == NULL && no_event_ok == 0) {
		fprintf(stderr, "oprofiled: no events specified.\n");
		poptPrintHelp(optcon, stderr, 0);
		exit(EXIT_FAILURE);
	}

	if (!xenimage || !strcmp("", xenimage)) {
		no_xen = 1;
	} else {
		no_xen = 0;

		/* canonicalise xen image filename. */
		tmp = xmalloc(PATH_MAX);
		if (realpath(xenimage, tmp))
			xenimage = tmp;
		else
			free(tmp);

		if (!xen_range || !strcmp("", xen_range)) {
			fprintf(stderr, "oprofiled: no Xen VMA range specified.\n");
			poptPrintHelp(optcon, stderr, 0);
			exit(EXIT_FAILURE);
		}
	}

	if (events != NULL)
		opd_parse_events(events);

	opd_parse_image_filter();

	poptFreeContext(optcon);
}


/* determine what kernel we're running and which daemon
 * to use
 */
static struct oprofiled_ops * get_ops(void)
{
	switch (op_get_interface()) {
#ifndef ANDROID
		case OP_INTERFACE_24:
			printf("Using 2.4 OProfile kernel interface.\n");
			return &opd_24_ops;
#endif
		case OP_INTERFACE_26:
			printf("Using 2.6+ OProfile kernel interface.\n");
			return &opd_26_ops;
		default:
			break;
	}

	fprintf(stderr, "Couldn't determine kernel version.\n");
	exit(EXIT_FAILURE);
	return NULL;
}


int main(int argc, char const * argv[])
{
	int err;
	struct rlimit rlim = { 2048, 2048 };

	opd_options(argc, argv);
	init_op_config_dirs(session_dir);

	opd_setup_signals();

	err = setrlimit(RLIMIT_NOFILE, &rlim);
	if (err)
		perror("warning: could not set RLIMIT_NOFILE to 2048: ");

	opd_write_abi();

	opd_ops = get_ops();

	opd_ops->init();

	opd_go_daemon();

	/* clean up every 10 minutes */
	alarm(60 * 10);

	if (op_write_lock_file(op_lock_file)) {
		fprintf(stderr, "oprofiled: could not create lock file %s\n",
			op_lock_file);
		exit(EXIT_FAILURE);
	}

	opd_ops->start();

	opd_ops->exit();

	return 0;
}