/*
 * Copyright (C) 2014 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License (not later!)
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not,  see <http://www.gnu.org/licenses>
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include <sys/time.h>
#include <sys/types.h>

#include "trace-local.h"

/*
 * Stream runs for a single machine. We are going to cheat
 * and use the trace-output and trace-input code to create
 * our pevent. First just create a trace.dat file and then read
 * it to create the pevent and handle.
 */
struct tracecmd_input *
trace_stream_init(struct buffer_instance *instance, int cpu, int fd, int cpus,
		  int profile, struct hook_list *hooks, int global)
{
	struct tracecmd_input *trace_input;
	struct tracecmd_output *trace_output;
	static FILE *fp = NULL;
	static int tfd;
	static int ofd;
	long flags;

	if (instance->handle) {
		trace_input = instance->handle;
		goto make_pipe;
	}

	if (!fp) {
		fp = tmpfile();
		if (!fp)
			return NULL;
		tfd = fileno(fp);

		ofd = dup(tfd);
		trace_output = tracecmd_create_init_fd(ofd);
		if (!trace_output) {
			fclose(fp);
			return NULL;
		}
		tracecmd_output_free(trace_output);
	}

	lseek(ofd, 0, SEEK_SET);

	trace_input = tracecmd_alloc_fd(ofd);
	if (!trace_input) {
		close(ofd);
		goto fail;
	}

	if (tracecmd_read_headers(trace_input) < 0)
		goto fail_free_input;

	if (profile)
		trace_init_profile(trace_input, hooks, global);

 make_pipe:
	/* Do not block on this pipe */
	flags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flags | O_NONBLOCK);

	if (tracecmd_make_pipe(trace_input, cpu, fd, cpus) < 0)
		goto fail_free_input;

	instance->handle = trace_input;

	return trace_input;

 fail_free_input:
	tracecmd_close(trace_input);
 fail:
	fclose(fp);

	return NULL;
}

int trace_stream_read(struct pid_record_data *pids, int nr_pids, struct timeval *tv,
		      int profile)
{
	struct pevent_record *record;
	struct pid_record_data *pid;
	struct pid_record_data *last_pid;
	fd_set rfds;
	int top_rfd = 0;
	int ret;
	int i;

	last_pid = NULL;

 again:
	for (i = 0; i < nr_pids; i++) {
		pid = &pids[i];

		if (!pid->record)
			pid->record = tracecmd_read_data(pid->instance->handle, pid->cpu);
		record = pid->record;
		if (!record && errno == EINVAL)
			/* pipe has closed */
			pid->closed = 1;

		if (record &&
		    (!last_pid || record->ts < last_pid->record->ts))
			last_pid = pid;
	}
	if (last_pid) {
		trace_show_data(last_pid->instance->handle, last_pid->record, profile);
		free_record(last_pid->record);
		last_pid->record = NULL;
		return 1;
	}

	FD_ZERO(&rfds);

	for (i = 0; i < nr_pids; i++) {
		/* Do not process closed pipes */
		if (pids[i].closed)
			continue;
		if (pids[i].brass[0] > top_rfd)
			top_rfd = pids[i].brass[0];

		FD_SET(pids[i].brass[0], &rfds);
	}

	ret = select(top_rfd + 1, &rfds, NULL, NULL, tv);

	if (ret > 0)
		goto again;

	return ret;
}
