/**
  * IBM IPR adapter dump daemon
  *
  * (C) Copyright 2000, 2004
  * International Business Machines Corporation and others.
  * All Rights Reserved. This program and the accompanying
  * materials are made available under the terms of the
  * Common Public License v1.0 which accompanies this distribution.
  *
  */

/*
 * $Header: /cvsroot/iprdd/iprutils/iprdump.c,v 1.19 2008/11/20 01:20:20 wboyer Exp $
 */

#ifndef iprlib_h
#include "iprlib.h"
#endif

#include <sys/mman.h>

#define IPRDUMP_SIZE (80 * 1024 * 1024)
#define MAX_DUMP_FILES 4
#define TOOL_NAME "iprdump"
#define DUMP_PREFIX TOOL_NAME"."

char *tool_name = TOOL_NAME;

struct dump_header {
	u32 eye_catcher;
#define IPR_DUMP_EYE_CATCHER 0xC5D4E3F2
	u32 total_length;
	u32 num_elems;
	u32 first_entry_offset;
	u32 status;
} *dump_header;

struct dump_entry_header {
	u32 length;
	u32 id;
#define IPR_DUMP_TEXT_ID     3
	char data[0];
};

struct dump {
	struct dump_header hdr;
	u8 data[IPRDUMP_SIZE-sizeof(struct dump_header)];
};

static struct dump dump;
static char usr_dir[100];
static char *enable = "1\n";
static char *disable = "0\n";

/**
 * enable_dump -
 * @ioa:		ipr_ioa struct
 *
 * Returns:
 *   nothing
 **/
static void enable_dump(struct ipr_ioa *ioa)
{
	int rc;

	rc = ipr_write_host_attr(ioa, "dump", enable, strlen(enable));
	if (rc != strlen(enable)) {
		ioa_err(ioa, "Failed to enable dump. rc=%d. %m\n", rc);
		return;
	}
}

/**
 * disable_dump -
 * @ioa:		ipr_ioa struct
 *
 * Returns:
 *   nothing
 **/
static void disable_dump(struct ipr_ioa *ioa)
{
	int rc;

	rc = ipr_write_host_attr(ioa, "dump", disable, strlen(disable));
	if (rc != strlen(disable)) {
		ioa_err(ioa, "Failed to disable dump. rc=%d. %m\n", rc);
		return;
	}
}

/**
 * read_dump -
 * @ioa:		ipr_ioa struct
 *
 * Returns:
 *   number of items read
 **/
static int read_dump(struct ipr_ioa *ioa)
{
	int count = 0;
	char path[PATH_MAX];
	FILE *file;

	sprintf(path, "/sys/class/scsi_host/%s/%s", ioa->host_name, "dump");
	file = fopen(path, "r");
	count = fread(&dump, 1 , sizeof(dump), file);
	fclose(file);

	return (count < 0) ? 0: count;
}

/**
 * select_dump_file -
 * @dirent:		dirent struct
 *
 * Returns:
 *   1 if the string is found, else 0
 **/
static int select_dump_file(const struct dirent *dirent)
{
	if (strstr(dirent->d_name, DUMP_PREFIX))
		return 1;
	return 0;
}

/**
 * dump_sort -
 * @a:		void buffer
 * @b:		void buffer
 *
 * Returns:
 *
 **/
static int dump_sort(const struct dirent **dumpa, const struct dirent **dumpb)
{
	int numa, numb;

	sscanf((*dumpa)->d_name, DUMP_PREFIX"%d", &numa);
	sscanf((*dumpb)->d_name, DUMP_PREFIX"%d", &numb);

	if (numa < MAX_DUMP_FILES && numb >= (100 - MAX_DUMP_FILES))
		return 1;
	if (numb < MAX_DUMP_FILES && numa >= (100 - MAX_DUMP_FILES))
		return -1;
	return alphasort(dumpa, dumpb);
}

/**
 * cleanup_old_dumps -
 *
 * Returns:
 *   Nothing
 **/
static void cleanup_old_dumps()
{
	struct dirent **dirent;
	char fname[100];
	int rc, i;

	rc = scandir(usr_dir, &dirent, select_dump_file, dump_sort);
	if (rc > 0) {
		for (i = 0 ; i < (rc - MAX_DUMP_FILES); i++) {
			sprintf(fname, "%s%s", usr_dir, dirent[i]->d_name);
			if (remove(fname)) {
				syslog(LOG_ERR, "Delete of %s%s failed. %m\n",
				       usr_dir, dirent[i]->d_name);
			}
		}

		free(*dirent);
	}
}

/**
 * get_dump_fname -
 * @fname:		file name
 *
 * Returns:
 *   -EIO on error, else 0
 **/
static int get_dump_fname(char *fname)
{
	struct dirent **dirent;
	int rc;
	char *tmp;
	int dump_id = 0;

	cleanup_old_dumps();
	rc = scandir(usr_dir, &dirent, select_dump_file, dump_sort);
	if (rc > 0) {
		rc--;
		tmp = strstr(dirent[rc]->d_name, DUMP_PREFIX);
		if (tmp) {
			tmp += strlen(DUMP_PREFIX);
			dump_id = strtoul(tmp, NULL, 10) + 1;
			if (dump_id > 99)
				dump_id = 0;
		} else {
			free(*dirent);
			fname[0] = '\0';
			return -EIO;
		}

		free(*dirent);
	}

	sprintf(fname, "%s%02d", DUMP_PREFIX, dump_id);
	return 0;
}

/**
 * write_dump -
 * @ioa:		ipr_ioa struct
 * @count:		size to write
 *
 * Returns:
 *   Nothing
 **/
static void write_dump(struct ipr_ioa *ioa, int count)
{
	int f_dump;
	char dump_file[100], dump_path[100];

	if (get_dump_fname(dump_file))
		return;

	sprintf(dump_path, "%s%s", usr_dir, dump_file);
	f_dump = creat(dump_path, S_IRUSR);
	if (f_dump < 0) {
		syslog(LOG_ERR, "Cannot open %s. %m\n", dump_path);
		return;
	}

	write(f_dump, &dump, count);
	close(f_dump);

	ioa_err(ioa, "Dump of ipr IOA has completed to file: %s\n", dump_path);
}

/**
 * handle_signal -
 * @signal:		signal value
 *
 * Returns:
 *   Nothing
 **/
static void handle_signal(int signal)
{
	struct ipr_ioa *ioa;

	tool_init(0);
	for_each_ioa(ioa)
		disable_dump(ioa);
	exit(0);
}

/**
 * select_dump_file -
 * @ioa:		ipr_ioa struct
 *
 * Returns:
 *   Nothing
 **/
static void dump_ioa(struct ipr_ioa *ioa)
{
	int count;

	count = read_dump(ioa);
	if (count <= 0)
		return;
	write_dump(ioa, count);
	disable_dump(ioa);
	enable_dump(ioa);
}

/**
 * poll_for_dump -
 *
 * Returns:
 *   Nothing
 **/
static void poll_for_dump()
{
	struct ipr_ioa *ioa;

	tool_init(0);

	for_each_ioa(ioa)
		enable_dump(ioa);
	for_each_ioa(ioa)
		dump_ioa(ioa);
}

/**
 * kevent_handler -
 * @buf:		character buffer
 *
 * Returns:
 *   Nothing
 **/
static void kevent_handler(char *buf)
{
	struct ipr_ioa *ioa;
	int host, i;
	char *index;

	if (!strncmp(buf, "change@/class/scsi_host", 23)) {
		host = strtoul(&buf[28], NULL, 10);
	} else if (!strncmp(buf, "change@/devices/pci", 19) && strstr(buf, "scsi_host")) {
		index = strrchr(buf, '/');
		host = strtoul(index + 5, NULL, 10);
	} else
		return;

	for (i = 0; i < 10; i++) {
		tool_init(0);
		ioa = find_ioa(host);
		if (ioa)
			break;
		sleep(2);
	}

	if (!ioa) {
		syslog(LOG_ERR, "Failed to find ipr ioa %d for iprdump\n", host);
		return;
	}

	enable_dump(ioa);
	dump_ioa(ioa);
}

/**
 * main -
 * @argc:		argument count
 * @argv:		argument string
 *
 * Returns:
 *   return value of handle_events() call
 **/
int main(int argc, char *argv[])
{
	struct ipr_ioa *ioa;
	int len, i;

	openlog(TOOL_NAME, LOG_PERROR | LOG_PID | LOG_CONS, LOG_USER);
	strcpy(usr_dir, IPRDUMP_DIR);

	for (i = 1; i < argc; i++) {
		if (parse_option(argv[i]))
			continue;
		if (strcmp(argv[i], "-d") == 0) {
			strcpy(usr_dir,argv[++i]);
			len = strlen(usr_dir);
			if (len < sizeof(usr_dir) && usr_dir[len] != '/') {
				usr_dir[len + 1] = '/';
				usr_dir[len + 2] = '\0';
			}
		} else {
			printf("Usage: "TOOL_NAME" [options]\n");
			printf("  Options: --version    Print iprdump version\n");
			printf("           --daemon     Run as a daemon\n");
			printf("           --debug      Print extra debugging information\n");
			printf("           -d <usr_dir>\n");
			exit(1);
		}
	}

	if (daemonize)
		ipr_daemonize();

	signal(SIGINT, handle_signal);
	signal(SIGQUIT, handle_signal);
	signal(SIGTERM, handle_signal);

	tool_init(0);
	for_each_ioa(ioa)
		enable_dump(ioa);

	return handle_events(poll_for_dump, 60, kevent_handler);
}
