/* $Id: lkstlogd.c,v 1.9 2002/06/28 11:27:53 indoh Exp $ */
/* command/lkstlogd.c */

/************************************************************
 *
 * COPYRIGHT (C) HITACHI,LTD. 2002 ALL RIGHTS RESERVED.
 * WRITTEN BY HITACHI SYSTEMS DEVELOPMENT LABORATORY,
 *            HITACHI CENTRAL RESEARCH LABORATORY.
 *
 * Created by T.Nakamura <tetsu@crl.hitachi.co.jp>
 *
 ************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

#include "command.h"
#define LKST_ETYPE_DEF_DESCRIPTIONS
#include <linux/lkst_events.h>

#define NAME	"lkstlogd"
#define VERSION "1.1"

#define LOG_HEADER_SIZE \
	(sizeof(log_header_t) +					\
	sizeof(struct posix_log_entry) +			\
	2*sizeof(int) + sizeof(char)*LKST_ARCH_NAME_LEN)


//#define DEBUG

int  owner_daemon();
int  child_daemon_main();
void child_buf_read();
void chk_limit_and_seek(FILE *, int);
int  file_write(FILE *, struct lkst_log_record*, int);
int  active_cpu(int, int *);
int  buf_read(int, struct lkst_log_record*, int);
int  buffer_read_size(int);
void cleanup();
void sighup_handler();
void die_handler(int);
void child_die_handler(int);
void par_snap_handler();
void par_switch_handler();
void child_switch_handler();
void child_end_snap_handler();
void child_snap_handler();
int  kill_children(int);
int  del_pid_file();
void usage(void);
void buffer_setrmod(int);
void buffer_ring(int, int);
void init_maskset(int);
void init_buffer(int);
void restore_maskset(void);
void restore_buffer(void);
void write_header(FILE*);

int read_on = 0;
int restart = 0;
int file_snum = 0;
int change_fd = 0;

struct timeval xtime;
lkst_tsc_t tsc;
u_int64_t cpu_hz;
int endian_big;
int buf_ver;
char arch[LKST_ARCH_NAME_LEN];

FILE *logfd ;
FILE *logfd_toclose=NULL ;
FILE *logfd_c ;

#ifndef LKSTLOGD_PID
# ifdef DEBUG
#  define LKSTLOGD_PID "./lkstlogd.pid"
# else
#  define LKSTLOGD_PID "/var/run/lkstlogd.pid"
# endif
#endif

#ifndef LKSTLOGD_LOGFILE
# ifdef DEBUG
#  define LKSTLOGD_LOGFILE "./sebuf"
# else
#  define LKSTLOGD_LOGFILE "/var/log/lkst/sebuf"
# endif
#endif

#define DEFAULT_LIMIT_SIZE	10*1024*1024	/* 10M byte */
#define INIT_BUF_SIZE_DEFAULT	2*1024*1024	/* 2Mbyte */
#define INIT_BUF_SIZE_MAX	256*1024*1024	/* 256Mbyte */
#define INIT_BUF_NUM_DEFAULT	2
#define INIT_BUF_NUM_MAX	10

static char pid_file[256] = LKSTLOGD_PID;
static char log_file[256] = LKSTLOGD_LOGFILE;

int init_buf_size = INIT_BUF_SIZE_DEFAULT;
int init_buf_num = INIT_BUF_NUM_DEFAULT;

int ncpu;	// number of forked process
int cpu_id;	// cpu id of child process

struct log_info {
	int c_pid;
	int buf_size;
	int limit_size;
	int total_size;
	lkst_buffer_id buf_id[INIT_BUF_NUM_MAX];
	lkst_buffer_id prev_id;
	int prev_size;
};

struct log_info info[LKST_CPU_MAX];

lkst_maskset_id maskset_id_prev = LKST_MASKSET_ID_VOID;
		// previous selected maskset id
lkst_maskset_id maskset_id_daemon;
		// daemon id of maskset 

int main(int argc, char** argv)
{
	int o_pid;	/* pid of the owner process */
	int pid;	/* pid of the child process */
  
	if( (pid = fork()) < 0) {	/* ^Z */
		printf("lkstlogd error: Unable to fork process\n");
		exit(1);
	} else if(pid > 0) {
		exit(0);
	}

	parse_arg(argc, argv);

	setsid();	/* become session leader */

	owner_daemon();

	cleanup();

	exit(0);
}

int parse_arg(int argc, char **argv)
{
	int ch;
	int i;

	for(i=0; i<LKST_CPU_MAX; i++){
		info[i].limit_size = DEFAULT_LIMIT_SIZE;
	}

	while ((ch = getopt(argc, argv, "ab:n:l:hvf:")) != EOF){
		switch((char)ch) {
		case 'a':
			read_on = 1;
			break;
		case 'b':
			init_buf_size = atoi(optarg);
			if(init_buf_size > INIT_BUF_SIZE_MAX) 
				init_buf_size = INIT_BUF_SIZE_MAX;
			break;
		case 'n':
			init_buf_num = atoi(optarg);
			if(init_buf_num > INIT_BUF_NUM_MAX) 
				init_buf_num = INIT_BUF_NUM_MAX;
			break;
		case 'l':
			for(i=0; i<LKST_CPU_MAX; i++)
				info[i].limit_size = atoi(optarg);
			break;
		case 'v':
			version(NAME, VERSION);
			break;
		case 'f':
			strncpy(log_file, optarg, 255);
			break;
		case 'h':
		case '?':
		default:
			usage();
		}
	}
}

int owner_daemon()
{
	int i;
	int bufsize[LKST_CPU_MAX];
	int devfd;

	FILE *pfd;

	/* Main loop */
	while(1)
	{
		/* check pid file */
		if ( (pfd = fopen(pid_file, "r")) != NULL){
			fprintf(stderr, "lkstlogd error: lkstlogd is already started.\n");
			exit(1);
		}
	
		/* write pid file */
		if ( (pfd = fopen(pid_file, "w")) == NULL){
			fprintf(stderr, "lkstlogd error: Can't open %s.\n", pid_file);
			exit(1);
		}
		if (!fprintf(pfd,"%d\n", getpid())) {
			fprintf(stderr, "lkstlogd error: Can't write %s.\n", pid_file);
			exit(1);
		}
		fclose(pfd);

		devfd = open_dev();
		/* set ncpu */
		active_cpu(devfd, bufsize);
		
		init_buffer(devfd);

		/* change maskset for daemon */
		init_maskset(devfd);
		close(devfd);

		/* set up sighandler */
		if(signal(SIGHUP, sighup_handler) == SIG_ERR) exit(1);
		if(signal(SIGINT, die_handler) == SIG_ERR) exit(1);
		if(signal(SIGTERM, die_handler) == SIG_ERR) exit(1);
		if(signal(SIGUSR1, par_snap_handler) == SIG_ERR) exit(1);
		if(signal(SIGUSR2, par_switch_handler) == SIG_ERR) exit(1);

		/* fork childlen */
		for(i=0; i<ncpu; i++){
			if( (info[i].c_pid = fork()) < 0) {
				printf("lkstlogd error: Unable to fork process\n");
				exit(1);
			} else if(!info[i].c_pid) {
				cpu_id = i;
				info[i].buf_size = bufsize[i];
				child_daemon_main();
			}
		}

		/* wait childlen */
		for(i=0; i<ncpu; i++){
			int status;
			waitpid((pid_t)info[i].c_pid, &status, 0);
		}

		if(restart == 0)
			break;

		restart = 0;
		
	}

	return 0;
}

int child_daemon_main()
{
	struct sigaction sa;
	char n[256];
	FILE *tmpfp;

	sprintf(n, "%s%02d.%01d", log_file, cpu_id, file_snum);
	if ( (tmpfp = fopen(n, "w")) == NULL){
		fprintf(stderr, "lkstlogd error: Can't open %s.\n", n);
		exit(1);
	}
	fclose(tmpfp);

	/* set up sighandler */
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_NODEFER;
	if(read_on == 0)
		sa.sa_handler = child_snap_handler;
	else
		sa.sa_handler = child_end_snap_handler;

	sigaction(SIGUSR1, &sa, NULL);

	if(signal(SIGUSR2, child_switch_handler) == SIG_ERR) exit(1);
	if(signal(SIGHUP, SIG_DFL) == SIG_ERR) exit(1);
	if(signal(SIGINT, SIG_DFL) == SIG_ERR) exit(1);
	if(signal(SIGTERM, child_die_handler) == SIG_ERR) exit(1);

	if(read_on == 1)
		child_buf_read();

	/* Waiting for SIGUSER1 */
	while (1)
		pause();

	return 0;
}

void chk_limit_and_seek(FILE *fd, int res_size){
	int ret=0;

	if(info[cpu_id].total_size > info[cpu_id].limit_size) {
		ret = fseek(fd, (off_t)LOG_HEADER_SIZE, SEEK_SET);
		if(ret<0) {
			fprintf(stderr, "lkstlogd error: Can't seek file pointer.\n");
			exit(1);
		} else {
			info[cpu_id].total_size = LOG_HEADER_SIZE + res_size;
		}

	}
}

void child_buf_read()
{
	char n[256];
	int read_size, res_size;
	int devfd;
	struct lkst_log_record *buffer;

	devfd = open_dev();

	buffer_setrmod(devfd);
	buffer_ring(devfd, 1);

	read_size = buffer_read_size(info[cpu_id].buf_size);

	sprintf(n, "%s%02d.%01d", log_file, cpu_id, file_snum);
	if ( (logfd = fopen(n, "w")) == NULL){
		fprintf(stderr, "lkstlogd error: Can't open %s.\n", n);
		exit(1);
	}

	buffer = (struct lkst_log_record *) malloc(read_size);
	if(!buffer){
		fprintf(stderr, "lkstlogd error: Can't allocate buffer area.\n");
		exit(1);
	}

	/* write log header */
	write_header(logfd);

	info[cpu_id].total_size = LOG_HEADER_SIZE;

	while(read_on){
		res_size = buf_read(devfd, buffer, read_size);
		if (res_size > 0) {
			info[cpu_id].total_size += res_size;
			chk_limit_and_seek(logfd, res_size);
			file_write(logfd, buffer, res_size);
			if (logfd_toclose) {
				fclose(logfd_toclose);
				logfd_toclose = NULL;
			}
		}
		if(change_fd == 1){
			logfd = logfd_c;
			info[cpu_id].total_size = LOG_HEADER_SIZE;
			change_fd = 0;
		}
	}

	buffer_ring(devfd, 0);
	fclose(logfd);
	close(devfd);
}


void write_header(FILE *fd)
{
	log_header_t logh = {
		log_magic: LOGFILE_MAGIC,
		log_version: 0
	};
	struct posix_log_entry posix_entry = {
		log_size: 2*sizeof(int) + sizeof(char)*LKST_ARCH_NAME_LEN,
		log_format:PXLOG_BINARY
	};
	fwrite(&logh, sizeof(log_header_t), 1, fd);
	fwrite(&posix_entry, sizeof(struct posix_log_entry), 1, fd);
	fwrite(&endian_big, sizeof(int), 1, fd);
	fwrite(&buf_ver, sizeof(int), 1, fd);
	fwrite(arch, sizeof(char)*LKST_ARCH_NAME_LEN, 1, fd);
}


inline void calc_logtime(struct lkst_log_record *buffer, int read_count)
{
	int j;
	struct timespec *buf, base, diff;
	lkst_tsc_t log_cnt, diff_cnt;

	base.tv_sec = xtime.tv_sec;
	base.tv_nsec = 1000 * xtime.tv_usec;

	for (j = 0; j < read_count; j++) {
	/* buffer[j].posix.log_time.tv_sec has upper 32bit of cpu counter
	   buffer[j].posix.log_time.tv_nsec has lower 32bit of cpu counter */

		buf = &(buffer[j].posix.log_time);

		log_cnt = ((u_int64_t) buf->tv_sec << 32) |
			(unsigned long) buf->tv_nsec;
		
		if (tsc > log_cnt){
			diff_cnt = tsc - log_cnt;
			diff.tv_sec  = (unsigned long) (diff_cnt / cpu_hz);
			diff.tv_nsec = (unsigned long) ((diff_cnt % cpu_hz) *
							1000000000 / cpu_hz);
			
			if (base.tv_nsec > diff.tv_nsec){
				buf->tv_sec  = base.tv_sec - diff.tv_sec;
				buf->tv_nsec = base.tv_nsec - diff.tv_nsec;
			} else {
				buf->tv_sec  = base.tv_sec - diff.tv_sec - 1;
				buf->tv_nsec = base.tv_nsec + 1000000000 -
					diff.tv_nsec;
			}
		} else {
			diff_cnt = log_cnt - tsc;
			diff.tv_sec  = (unsigned long) (diff_cnt / cpu_hz);
			diff.tv_nsec = (unsigned long) ((diff_cnt % cpu_hz) *
							1000000000 / cpu_hz);
			
			if(base.tv_nsec + diff.tv_nsec >= 1000000000){
				buf->tv_sec  = base.tv_sec + diff.tv_sec + 1;
				buf->tv_nsec = base.tv_nsec + diff.tv_nsec -
					1000000000;
			} else{
				buf->tv_sec  = base.tv_sec + diff.tv_sec;
				buf->tv_nsec = base.tv_nsec + diff.tv_nsec;
			}
		}
	}
}


int file_write(FILE *fd, struct lkst_log_record *buffer, int res_size)
{
	int read_count;

	read_count = res_size / sizeof(struct lkst_log_record);

	/* conver mc to time */
	calc_logtime(buffer, read_count);

	/* write to file */
	fwrite(buffer, sizeof(struct lkst_log_record), read_count, fd);
}


int active_cpu(int devfd, int *bufsize)
{
	int retval;
	int i;
	
	struct lkst_buffer_listparam lp;
	struct lkst_buffer_listent listent[LKST_BUFFER_TBL_MAX];

	for (i = 0; i < LKST_CPU_MAX; i++) {
		bufsize[i] = 0;
	}

	lp.listent_size = LKST_BUFFER_LISTENT_SIZE(LKST_BUFFER_TBL_MAX);
	lp.listent = listent;

	retval = ioctl(devfd, LKST_IOC_BUFFER_LIST, &lp);
	
	if (retval) {
		fprintf(stderr, "lkstlogd error: ioctl(LKST_IOC_BUFFER_LIST) error: %s\n", strerror(errno));
		exit(1);
	}

	ncpu = 0;
	for (i = 0; i < LKST_BUFFER_TBL_MAX; i++) {
		if (lp.listent[i].id != LKST_BUFFER_ID_VOID) {
			/* count ncpu */
			if(lp.listent[i].current_flag == 1){
				ncpu++;
			}
			/* culc max buf size */
			if (bufsize[lp.listent[i].cpu] <  lp.listent[i].size)
				bufsize[lp.listent[i].cpu] = lp.listent[i].size;
		}
	}
}

void init_buffer(int devfd)
{
	int ret, i, j, k;
 	struct lkst_buffer_param param;
	struct lkst_buffer_listparam lp;
	struct lkst_buffer_listent listent[LKST_BUFFER_TBL_MAX];

	for (i=0; i<ncpu; i++) {
		for (j=0; j<init_buf_num; j++){
			param.id = LKST_BUFFER_ID_VOID;
			param.size = init_buf_size;
			param.cpu = i;

			ret = ioctl(devfd, LKST_IOC_BUFFER_CREATE, &param);
			if (ret) {
				fprintf(stderr, "lkstlogd error: ioctl(LKST_IOC_BUFFER_CREATE) error: %s\n", strerror(errno));
				exit(1);
			}

			info[i].buf_id[j] = param.id;
		}
	}

	lp.listent_size = LKST_BUFFER_LISTENT_SIZE(LKST_BUFFER_TBL_MAX);
	lp.listent = listent;
	ret = ioctl(devfd, LKST_IOC_BUFFER_LIST, &lp);
	
	for (i=0; i<ncpu && init_buf_num>0; i++) {
	  	for (k = 0; k < LKST_BUFFER_TBL_MAX; k++) {
			if (lp.listent[k].id == info[i].buf_id[0]) {
				info[i].prev_id = lp.listent[k].prev;
				info[i].prev_size = lp.listent[lp.listent[k].prev].size;
			}
		}
	}

}

void restore_buffer(void)
{
	int devfd;
	int ret, i, j;
 	struct lkst_buffer_param param;

	devfd = open_dev();
	
	for (i=0; i<ncpu; i++) {
		ret = ioctl(devfd, LKST_IOC_BUFFER_DELETE, info[i].prev_id);
		if(!ret){
			param.id = info[i].prev_id;
			param.size = info[i].prev_size;
			param.cpu = i;
			ret = ioctl(devfd, LKST_IOC_BUFFER_CREATE, &param);
		}

		for (j=0; j<init_buf_num; j++){
		retry:
			ret = ioctl(devfd, LKST_IOC_BUFFER_DELETE, info[i].buf_id[j]);
			if (ret == EBUSY) {
				ret = ioctl(devfd, LKST_IOC_BUFFER_SHIFT, i);
				goto retry;
			} else if (ret) {
				fprintf(stderr, "lkstlogd error: ioctl(LKST_IOC_BUFFER_DELETE) error: %s\n", strerror(errno));
				exit(1);
			}
		}
	}
}

void init_maskset(int devfd)
{
	int i, retval;
	struct lkst_maskset_param param;
	struct lkst_maskset_body maskset;
	struct lkst_maskset_entry *entry;

	/* read current maskset */
	param.id = LKST_MASKSET_ID_VOID;
	param.maskset_size = LKST_MASKSET_SIZE(LKST_MASKSET_TABLE_LEN_MAX);
	param.maskset = &maskset;

	retval = ioctl(devfd, LKST_IOC_MASKSET_READ, &param);
	if (retval) {
		fprintf(stderr, "lkstlogd error: ioctl(LKST_IOC_MASKSET_READ) error: %s\n", strerror(errno));
		exit(1);
	}
	maskset_id_prev = param.id;

	/* change buffer overflow event handler */
	for (i = 0; i < maskset.len; i++) {
		entry = &maskset.entry[i];
		if (entry->event_type == LKST_ETYPE_LKST_BUFF_OVFLOW) {
			entry->id = LKST_EVHANDLER_ID_BUFFER_SHIFT_DW;
			break;
		}
	}
	if (i == maskset.len) {
		fprintf(stderr, "lkstlogd error: Cannot initialize maskset for daemon!\n");
		exit(1);
	}

	/* write new maskset */
	param.id = LKST_MASKSET_ID_VOID;
	if (strlen(maskset.name) < (LKST_MASKSET_NAME_LEN - 8))
		strncat(maskset.name, "_DAEMON", 7);
	retval = ioctl(devfd, LKST_IOC_MASKSET_WRITE, &param);
	if (retval) {
		fprintf(stderr, "lkstlogd error: ioctl(LKST_IOC_MASKSET_WRITE) error: %s\n", strerror(errno));
		fprintf(stderr, "lkstlogd error: Maybe, lock events are recording, or maskset id is full.\n"); 
		del_pid_file();
	}

	maskset_id_daemon = param.id;

	/* change maskset */
	retval = ioctl(devfd, LKST_IOC_MASKSET_SET, maskset_id_daemon);
	if (retval) {
		fprintf(stderr, "lkstlogd error: ioctl(LKST_IOC_MASKSET_SET) error: %s\n", strerror(errno));
		exit(1);
	}
}

void restore_maskset(void)
{
	int devfd;
	int retval;

	if (maskset_id_prev != LKST_MASKSET_ID_VOID) {
		devfd = open_dev();
		/* restore maskset */
		retval = ioctl(devfd, LKST_IOC_MASKSET_SET, maskset_id_prev);
		if (retval) {
			fprintf(stderr, "lkstlogd error: ioctl(LKST_IOC_MASKSET_SET) error: %s\n", strerror(errno));
			fprintf(stderr, "lkstlogd error: Cannot restore maskset!\n");
			exit(1);
		}
		/* delete daemon maskset */
		retval = ioctl(devfd, LKST_IOC_MASKSET_DELETE, maskset_id_daemon);
		if (retval) {
			fprintf(stderr, "lkstlogd error: ioctl(LKST_IOC_MASKSET_DELETE) error: %s\n", strerror(errno));
			fprintf(stderr, "lkstlogd error: Cannot delete maskset for daemon!\n");
			exit(1);
		}
		close(devfd);
	}
}

int buf_read(int devfd, struct lkst_log_record *buffer, int read_size)
{
	int result;

	result = read(devfd, buffer, read_size);
	if(result < 0){
		fprintf(stderr, "lkstlogd error: lkst device read() error: %s\n", strerror(errno));
		perror("read");
	}
  
	return result;
}

int buffer_read_size(int buf_size)
{
	return buf_size * sizeof(struct lkst_log_record) / LKST_SIZEOF_LKST_EVENT_RECORD;
}

void cleanup()
{
	read_on = 0;

	kill_children(SIGTERM);

	restore_maskset();
	restore_buffer();

	del_pid_file();
}

void sighup_handler()
{
	cleanup();
	restart = 1;
	
	return;
}

void die_handler(int sigid)
{
	cleanup();
	exit(0);
}

void child_die_handler(int sigid)
{
	read_on = 0;
	exit(0);
}

void par_snap_handler()
{
	kill_children(SIGUSR1);
	signal(SIGUSR1, par_snap_handler);

	return;
}

void par_switch_handler()
{
	kill_children(SIGUSR2);
	signal(SIGUSR2, par_switch_handler);

	return;
}

void child_switch_handler()
{
	FILE *fd;
	char n[256];

	file_snum++;
	sprintf(n, "%s%02d.%01d", log_file, cpu_id, file_snum);
	if ( (fd = fopen(n, "w")) == NULL){
		fprintf(stderr, "lkstlogd error: Can't open %s.\n", n);
		exit(1);
	}

	/* write log header */
	write_header(fd);

	logfd_toclose = logfd;
	
	if(read_on == 0) {
		info[cpu_id].total_size = LOG_HEADER_SIZE;
		logfd=fd;
	}else{
		change_fd = 1;
		logfd_c=fd;
	}

	signal(SIGUSR2, child_switch_handler);
}

void child_end_snap_handler()
{
	struct sigaction sa;

	/* set up sighandler */
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_NODEFER;
	sa.sa_handler = child_snap_handler;
	sigaction(SIGUSR1, &sa, NULL);

	read_on = 0;

	return;
}

void child_snap_handler()
{
	struct sigaction sa;

	/* set up sighandler */
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sa.sa_handler = child_end_snap_handler;
	sigaction(SIGUSR1, &sa, NULL);

	read_on = 1;

	child_buf_read();

	return;
}

void buffer_setrmod(int devfd)
{
	int ret;
	struct lkst_buffer_srmodparam sp;

	sp.cpu = cpu_id;
	sp.mode = STD;

	if (ret = ioctl(devfd, LKST_IOC_BUFFER_SETRMOD, &sp)) {
		fprintf(stderr, "lkstlogd error: ioctl(LKST_IOC_BUFFER_SETRMOD) error: %s\n", strerror(errno));
		exit(1);
	}

	xtime = sp.xtime;
	tsc = sp.tsc;
	cpu_hz = sp.cpu_freq * 1000; /* convert kHz to Hz */
	endian_big = sp.endian_big;
	buf_ver = sp.buf_ver;
	strncpy(arch, sp.arch, LKST_ARCH_NAME_LEN);
}

void buffer_ring(int devfd, int ring)
{
	struct lkst_buffer_ringparam rp;
	int retval=0;

	rp.cpu = cpu_id;
        rp.ring = ring;
  
        retval = ioctl(devfd, LKST_IOC_BUFFER_RING, &rp); /* buffer_ring */

        if (retval) {
                fprintf(stderr, "lkstlogd error: ioctl(LKST_IOC_BUFFER_RING) error: %s\n", strerror(errno));
                exit(1);
        }
}

int kill_children(int sigid)
{
	int i;

	for(i=0; i<ncpu; i++)
		kill(info[i].c_pid, sigid);

	return 0;
}

int del_pid_file(){
	/* pid_file remove */
	if ( unlink((char *)pid_file) != 0){
		fprintf(stderr, "lkstlogd error: Can't remove %s.\n", pid_file);
	}
	return 0;
}

void usage(void)
{
	printf("Usage:lkstlogd [-a] [-l file_size_limit(byte)]\n");
	printf("\t[-b initial buffer size(byte)] \n");
	printf("\t[-n initial buffer num(byte)] \n");
	printf("\t[-f log_file_name] \n");
	exit(0);
}
