/**
 * @file daemon/liblegacy/init.c
 * Daemon set up and main loop for 2.4
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 */

#include "config.h"
 
#include "opd_proc.h"
#include "opd_mapping.h"
#include "opd_24_stats.h"
#include "opd_sample_files.h"
#include "opd_image.h"
#include "opd_parse_proc.h"
#include "opd_kernel.h"
#include "opd_printf.h"
#include "oprofiled.h"

#include "op_sample_file.h"
#include "op_config_24.h"
#include "op_interface.h"
#include "op_libiberty.h"
#include "op_deviceio.h"
#include "op_events.h"
#include "op_get_time.h"
#include "op_fileio.h"

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

fd_t hashmapdevfd;

int cpu_number;

static fd_t devfd;
static fd_t notedevfd;
static struct op_buffer_head * sbuf;
static size_t s_buf_bytesize;
static struct op_note * nbuf;
static size_t n_buf_bytesize;

static void opd_sighup(void);
static void opd_alarm(void);
static void opd_sigterm(void);


/**
 * op_open_files - open necessary files
 *
 * Open the device files and the log file,
 * and mmap() the hash map.
 */
static void op_open_files(void)
{
	hashmapdevfd = op_open_device(op_hash_device);
	if (hashmapdevfd == -1) {
		perror("Failed to open hash map device");
		exit(EXIT_FAILURE);
	}

	notedevfd = op_open_device(op_note_device);
	if (notedevfd == -1) {
		if (errno == EINVAL)
			fprintf(stderr, "Failed to open note device. Possibly you have passed incorrect\n"
				"parameters. Check /var/log/messages.");
		else
			perror("Failed to open note device");
		exit(EXIT_FAILURE);
	}

	devfd = op_open_device(op_device);
	if (devfd == -1) {
		if (errno == EINVAL)
			fprintf(stderr, "Failed to open device. Possibly you have passed incorrect\n"
				"parameters. Check /var/log/messages.");
		else
			perror("Failed to open profile device");
		exit(EXIT_FAILURE);
	}

	opd_init_hash_map();

	/* give output before re-opening stdout as the logfile */
	printf("Using log file %s\n", op_log_file);

	/* set up logfile */
	close(0);
	close(1);

	if (open("/dev/null", O_RDONLY) == -1) {
		perror("oprofiled: couldn't re-open stdin as /dev/null: ");
		exit(EXIT_FAILURE);
	}

	opd_open_logfile();

	printf("oprofiled started %s", op_get_time());
	fflush(stdout);
}
 

static void opd_do_samples(struct op_buffer_head const * buf);
static void opd_do_notes(struct op_note const * opd_buf, size_t count);

/**
 * do_shutdown - shutdown cleanly, reading as much remaining data as possible.
 * @param buf  sample buffer area
 * @param size  size of sample buffer
 * @param nbuf  note buffer area
 * @param nsize  size of note buffer
 */
static void opd_shutdown(struct op_buffer_head * buf, size_t size, struct op_note * nbuf, size_t nsize)
{
	ssize_t count = -1;
	ssize_t ncount = -1;

	/* the dump may have added no samples, so we must set
	 * non-blocking */
	if (fcntl(devfd, F_SETFL, fcntl(devfd, F_GETFL) | O_NONBLOCK) < 0) {
		perror("Failed to set non-blocking read for device: ");
		exit(EXIT_FAILURE);
	}

	/* it's always OK to read the note device */
	while (ncount < 0)
		ncount = op_read_device(notedevfd, nbuf, nsize);

	if (ncount > 0)
		opd_do_notes(nbuf, ncount);

	/* read as much as we can until we have exhausted the data
	 * (EAGAIN is returned).
	 *
	 * This will not livelock as the profiler has been partially
	 * shut down by now.
	 */
	while (1) {
		count = op_read_device(devfd, buf, size);
		if (count < 0 && errno == EAGAIN)
			break;
		verbprintf(vmisc, "Shutting down, state %d\n", buf->state);
		opd_do_samples(buf);
	}
}
 

/**
 * opd_do_read - enter processing loop
 * @param buf  buffer to read into
 * @param size  size of buffer
 * @param nbuf  note buffer
 * @param nsize  size of note buffer
 *
 * Read some of a buffer from the device and process
 * the contents.
 */
static void opd_do_read(struct op_buffer_head * buf, size_t size, struct op_note * nbuf, size_t nsize)
{
	while (1) {
		ssize_t count = -1;
		ssize_t ncount = -1;

		/* loop to handle EINTR */
		while (count < 0)
			count = op_read_device(devfd, buf, size);

		while (ncount < 0)
			ncount = op_read_device(notedevfd, nbuf, nsize);

		opd_do_notes(nbuf, ncount);
		opd_do_samples(buf);

		// we can lost a signal alarm or a signal hup but we don't
		// take care.
		if (signal_alarm) {
			signal_alarm = 0;
			opd_alarm();
		}

		if (signal_hup) {
			signal_hup = 0;
			opd_sighup();
		}

		if (signal_term)
			opd_sigterm();
 
		/* request to stop arrived */
		if (buf->state == STOPPING) {
			verbprintf(vmisc, "Shutting down by request.\n");
			opd_shutdown(buf, size, nbuf, nsize);
			return;
		}
	}
}

/**
 * opd_do_notes - process a notes buffer
 * @param opd_buf  buffer to process
 * @param count  number of bytes in buffer
 *
 * Process a buffer of notes.
 */
static void opd_do_notes(struct op_note const * opd_buf, size_t count)
{
	uint i;
	struct op_note const * note;

	for (i = 0; i < count/sizeof(struct op_note); i++) {
		note = &opd_buf[i];

		opd_24_stats[OPD_NOTIFICATIONS]++;

		switch (note->type) {
			case OP_MAP:
			case OP_EXEC:
				if (note->type == OP_EXEC)
					opd_handle_exec(note->pid, note->tgid);
				opd_handle_mapping(note);
				break;

			case OP_FORK:
				opd_handle_fork(note);
				break;

			case OP_DROP_MODULES:
				opd_clear_module_info();
				break;

			case OP_EXIT:
				opd_handle_exit(note);
				break;

			default:
				fprintf(stderr, "Received unknown notification type %u\n", note->type);
				abort();
				break;
		}
	}
}

/**
 * opd_do_samples - process a sample buffer
 * @param opd_buf  buffer to process
 *
 * Process a buffer of samples.
 * The signals specified by the global variable maskset are
 * masked.
 *
 * If the sample could be processed correctly, it is written
 * to the relevant sample file. Additionally mapping and
 * process notifications are handled here.
 */
static void opd_do_samples(struct op_buffer_head const * opd_buf)
{
	uint i;
	struct op_sample const * buffer = opd_buf->buffer; 

	opd_24_stats[OPD_DUMP_COUNT]++;

	verbprintf(vmisc, "Read buffer of %d entries for cpu %d.\n",
		   (unsigned int)opd_buf->count, opd_buf->cpu_nr);
 
	if (separate_cpu)
		cpu_number = opd_buf->cpu_nr;
	for (i = 0; i < opd_buf->count; i++) {
		verbprintf(vsamples, "%.6u: EIP: 0x%.8lx pid: %.6d\n",
			i, buffer[i].eip, buffer[i].pid);
		opd_put_sample(&buffer[i]);
	}
}


/**
 * opd_alarm - clean up old procs, msync, and report stats
 */
static void opd_alarm(void)
{
	opd_sync_samples_files();

	opd_age_procs();

	opd_print_24_stats();

	alarm(60 * 10);
}
 

/* re-open logfile for logrotate */
static void opd_sighup(void)
{
	printf("Received SIGHUP.\n");
	close(1);
	close(2);
	opd_open_logfile();
	/* We just close them, and re-open them lazily as usual. */
	opd_for_each_image(opd_close_image_samples_files);
}


static void clean_exit(void)
{
	opd_cleanup_hash_name();
	op_free_events();
	unlink(op_lock_file);
}


static void opd_sigterm(void)
{
	opd_print_24_stats();
	printf("oprofiled stopped %s", op_get_time());
	exit(EXIT_FAILURE);
}
 

static void opd_24_init(void)
{
	size_t i;
	int opd_buf_size = OP_DEFAULT_BUF_SIZE;
	int opd_note_buf_size = OP_DEFAULT_NOTE_SIZE;

	if (!no_vmlinux)
		opd_parse_kernel_range(kernel_range);
	opd_buf_size = opd_read_fs_int(OP_MOUNT, "bufsize", 1);
	opd_note_buf_size = opd_read_fs_int(OP_MOUNT, "notesize", 1);

	s_buf_bytesize = sizeof(struct op_buffer_head) + opd_buf_size * sizeof(struct op_sample);

	sbuf = xmalloc(s_buf_bytesize);

	n_buf_bytesize = opd_note_buf_size * sizeof(struct op_note);
	nbuf = xmalloc(n_buf_bytesize);

	opd_init_images();
	opd_init_procs();
	opd_init_kernel_image();

	for (i = 0; i < OPD_MAX_STATS; i++)
		opd_24_stats[i] = 0;

	if (atexit(clean_exit)) {
		perror("oprofiled: couldn't set exit cleanup: ");
		exit(EXIT_FAILURE);
	}
}


static void opd_24_start(void)
{
	op_open_files();

	/* yes, this is racey. */
	opd_get_ascii_procs();

	/* simple sleep-then-process loop */
	opd_do_read(sbuf, s_buf_bytesize, nbuf, n_buf_bytesize);
}


static void opd_24_exit(void)
{
	opd_print_24_stats();
	printf("oprofiled stopped %s", op_get_time());

	free(sbuf);
	free(nbuf);
	opd_clear_module_info();
	opd_proc_cleanup();
	/* kernel/module image are not owned by a proc, we must cleanup them */
	opd_for_each_image(opd_delete_image);
}


struct oprofiled_ops opd_24_ops = {
	.init = opd_24_init,
	.start = opd_24_start,
	.exit = opd_24_exit
};