/*
 * array-info - Array Controllers Informations
 * Copyright (C) 2002  Benoit Gaussen (ben@trez42.net)
 *
 * $Log: md_info.c,v $
 * Revision 1.8  2007/02/07 17:17:43  pere
 * Changed the 'status is ok' message for the md plugin to match the one
 * in the cciss and ida plugin, and make the string common across all
 * plugins.
 *
 * Revision 1.7  2007/02/03 10:04:05  pere
 * Convert the cciss and ida plugin to the new shared library framework.  Some code cleanup.
 *
 * Revision 1.6  2007/02/01 14:43:26  pere
 * Rewrite plugin API to pass driver data between the functions.
 *
 * Revision 1.5  2007/01/31 13:32:29  pere
 * Remove the need to link libarray-info into plugins by providing the functions needed in a list of callbacks.
 *
 * Revision 1.4  2007/01/25 23:09:33  pere
 * Reduce the number of exported symbols to make sure the array_plugin interface is used.
 *
 * Revision 1.3  2007/01/25 23:02:41  pere
 * Correct the prototypes.
 *
 * Revision 1.2  2002/07/30 14:12:46  trez42
 * Functions add and show infos
 *
 * Revision 1.1  2002/07/29 16:50:19  trez42
 * Add plugin support
 * Change directory structure
 *
 * Revision 1.12  2002/07/25 16:51:14  trez42
 * CVS Fixes
 *
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <time.h>

#include "array_plugin.h"

#include "md_info.h"
#include "md_data.h"

static device_major_t md_device_majors[] = { {BLOCK_DEVICE, MD_MAJOR, -1}
, {0, 0, 0}
};
static md_ctrl_data_t md_ctrl_data;

/* RAID level */
static const char *md_raid_level_str[] = {
	// MD_RAID_RAID0
	"RAID 0 (Stripping)",
	// MD_RAID_RAID1
	"RAID 1 (Mirroring)",
	// MD_RAID_RAID5
	"RAID 5",
	// MD_RAID_LINEAR
	"Linear",
	// MD_RAID_TRANSLUCENT
	"Translucent",
	// MD_RAID_HSM
	"HSM",
	// MD_RAID_MULTIPATH
	"Multipath",
	// MD_RAID_UNKNOWN
	"Unknown RAID type"
};

#include "xmalloc.ic"

static driver_data_ptr_t
md_ctrl_open(array_data_t * array_data)
{
	md_ctrl_data.fd = array_data->fd;

	return &md_ctrl_data;
}

static int
md_ctrl_close(driver_data_ptr_t data)
{
	return 0;
}

static char *
md_description(void)
{
	static char description[35];

	snprintf(description, 35, "MD Linux driver plugin v%d.%d.%d",
		 MD_INFO_VERSION >> 16 & 0xff, MD_INFO_VERSION >> 8 & 0xff,
		 MD_INFO_VERSION & 0xff);
	return description;
}

static int
md_cmd(int fd, int opcode, void *data)
{
	int ret;

	if ((ret = ioctl(fd, opcode, data)) < 0) {
		perror("ioctl");
		return ret;
	}

	return ret;
}

static void
md_disks_req(void)
{
	int i;

	if (!(md_ctrl_data.md_ctrl.ldrv.disks =
	      xmalloc(sizeof (struct mdu_disk_info_s) *
		      md_ctrl_data.md_ctrl.ldrv.nr_disks)))
		return;
	for (i = 0; i < md_ctrl_data.md_ctrl.ldrv.nr_disks; i++) {
		md_ctrl_data.md_ctrl.ldrv.disks[i].number = i;
		if (md_cmd
		    (md_ctrl_data.fd, GET_DISK_INFO,
		     &md_ctrl_data.md_ctrl.ldrv.disks[i])) {
			printf
			    ("Warning: error while getting MD device %d informations...\n",
			     i);
			continue;
		}
	}
}

static int
md_ctrl_req(driver_data_ptr_t data)
{
	struct mdu_array_info_s md_info;
	unsigned long dev_size;
	int raid_level;

	if (md_cmd(md_ctrl_data.fd, GET_ARRAY_INFO, &md_info) != 0) {
		printf("Error while getting MD device informations...\n");
		return -1;
	}
	if (md_cmd(md_ctrl_data.fd, BLKGETSIZE, &dev_size) != 0) {
		printf("Error while getting MD device size...\n");
		return -1;
	}

	md_ctrl_data.md_ctrl.vers_major = md_info.major_version;
	md_ctrl_data.md_ctrl.vers_minor = md_info.minor_version;
	md_ctrl_data.md_ctrl.vers_patchlevel = md_info.patch_version;
	md_ctrl_data.md_ctrl.ldrv.array_size = dev_size;
	md_ctrl_data.md_ctrl.ldrv.device_size = md_info.size;
	md_ctrl_data.md_ctrl.ldrv.creation_time = md_info.ctime;
	md_ctrl_data.md_ctrl.ldrv.nr_disks = md_info.nr_disks;
	md_ctrl_data.md_ctrl.ldrv.raid_disks = md_info.raid_disks;
	md_ctrl_data.md_ctrl.ldrv.active_disks = md_info.active_disks;
	md_ctrl_data.md_ctrl.ldrv.working_disks = md_info.working_disks;
	md_ctrl_data.md_ctrl.ldrv.failed_disks = md_info.failed_disks;
	md_ctrl_data.md_ctrl.ldrv.spare_disks = md_info.spare_disks;

	switch (md_info.level) {
	case -4:
		raid_level = MD_RAID_MULTIPATH;
		break;
	case -3:
		raid_level = MD_RAID_HSM;
		break;
	case -2:
		raid_level = MD_RAID_TRANSLUCENT;
		break;
	case -1:
		raid_level = MD_RAID_LINEAR;
		break;
	case 0:
		raid_level = MD_RAID_RAID0;
		break;
	case 1:
		raid_level = MD_RAID_RAID1;
		break;
	case 5:
		raid_level = MD_RAID_RAID5;
		break;
	default:
		raid_level = MD_RAID_UNKNOWN;
	}
	md_ctrl_data.md_ctrl.ldrv.fault_tol = raid_level;

	md_ctrl_data.md_ctrl.ldrv.status = md_info.state;

	md_disks_req();
	return 0;
}

static array_infos_t *
md_ctrl_infos(driver_data_ptr_t data, array_plugin_callbacks_t * callbacks)
{
	array_infos_t *md_infos = NULL;

	callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_CTRL,
				   "MD Linux driver\n");
	callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_CTRL,
				   "Version %d.%d.%d\n",
				   md_ctrl_data.md_ctrl.vers_major,
				   md_ctrl_data.md_ctrl.vers_minor,
				   md_ctrl_data.md_ctrl.vers_patchlevel);
	callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_LDRV,
				   "Creation time   : %s\n",
				   ctime((time_t *) & md_ctrl_data.md_ctrl.ldrv.
					 creation_time));
	callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_LDRV,
				   "Fault tolerance : %s\n",
				   md_raid_level_str[md_ctrl_data.md_ctrl.ldrv.
						     fault_tol]);
	callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_LDRV,
				   "Array Size      : %2.2f GiB\n",
				   (double) md_ctrl_data.md_ctrl.ldrv.
				   array_size * 512 / 1024 / 1024 / 1024);

	if (md_ctrl_data.md_ctrl.ldrv.status &
	    (1 << MD_SB_CLEAN | 1 << MD_SB_ERRORS)) {
		callbacks->array_add_infos(&md_infos,
					   ARRAY_INFO_LEVEL_LDRV_STATUS,
					   "Status          : %s\n",
					   LOGICAL_VOLUME_OK);
	} else {
		callbacks->array_add_infos(&md_infos,
					   ARRAY_INFO_LEVEL_LDRV_STATUS,
					   "Status          : Logical drive is %sclean, has %serrors\n",
					   (md_ctrl_data.md_ctrl.ldrv.
					    status & (1 << MD_SB_CLEAN)) ? "" :
					   "not ",
					   (md_ctrl_data.md_ctrl.ldrv.
					    status & (1 << MD_SB_ERRORS)) ? "" :
					   "no ");
	}

	callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_LDRV,
				   "Devices         : %d\n",
				   md_ctrl_data.md_ctrl.ldrv.nr_disks);

	if (md_ctrl_data.md_ctrl.ldrv.fault_tol >= MD_RAID_RAID0
	    && md_ctrl_data.md_ctrl.ldrv.fault_tol <= MD_RAID_RAID5)
		callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_LDRV,
					   "\tSize    : %2.2f GiB\n",
					   (double) md_ctrl_data.md_ctrl.ldrv.
					   device_size / 1024 / 1024);
	callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_LDRV,
				   "\tRaid    : %d\n",
				   md_ctrl_data.md_ctrl.ldrv.raid_disks);
	callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_LDRV,
				   "\tActive  : %d\n",
				   md_ctrl_data.md_ctrl.ldrv.active_disks);
	callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_LDRV,
				   "\tWorking : %d\n",
				   md_ctrl_data.md_ctrl.ldrv.working_disks);
	callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_LDRV,
				   "\tFailed  : %d\n",
				   md_ctrl_data.md_ctrl.ldrv.failed_disks);
	callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_LDRV,
				   "\tSpare   : %d\n",
				   md_ctrl_data.md_ctrl.ldrv.spare_disks);
	if (md_ctrl_data.md_ctrl.ldrv.nr_disks) {
		int i;

		callbacks->array_add_infos(&md_infos, ARRAY_INFO_LEVEL_LDRV,
					   "  RaidDevice   State\n");
		for (i = 0; i < md_ctrl_data.md_ctrl.ldrv.nr_disks; i++) {
			callbacks->array_add_infos(&md_infos,
						   ARRAY_INFO_LEVEL_LDRV,
						   "  %10d   ",
						   md_ctrl_data.md_ctrl.ldrv.
						   disks[i].raid_disk);
			if (md_ctrl_data.md_ctrl.ldrv.disks[i].
			    state & (1 << MD_DISK_ACTIVE))
				callbacks->array_add_infos(&md_infos,
							   ARRAY_INFO_LEVEL_LDRV,
							   "active ");
			if (md_ctrl_data.md_ctrl.ldrv.disks[i].
			    state & (1 << MD_DISK_FAULTY))
				callbacks->array_add_infos(&md_infos,
							   ARRAY_INFO_LEVEL_LDRV,
							   "faulty ");
			if (md_ctrl_data.md_ctrl.ldrv.disks[i].
			    state & (1 << MD_DISK_SYNC))
				callbacks->array_add_infos(&md_infos,
							   ARRAY_INFO_LEVEL_LDRV,
							   "synchronized ");
			if (md_ctrl_data.md_ctrl.ldrv.disks[i].
			    state & (1 << MD_DISK_REMOVED))
				callbacks->array_add_infos(&md_infos,
							   ARRAY_INFO_LEVEL_LDRV,
							   "removed ");
			callbacks->array_add_infos(&md_infos,
						   ARRAY_INFO_LEVEL_LDRV, "\n");
		}
	}

	return md_infos;
}

static void
md_ctrl_free(driver_data_ptr_t data)
{
	free(md_ctrl_data.md_ctrl.ldrv.disks);
	md_ctrl_data.md_ctrl.ldrv.disks = NULL;
}

array_plugin_t array_plugin = {
	.device_majors = md_device_majors,
	.plugin_version = MD_INFO_VERSION,
	.array_info_version = ARRAY_INFO_VERSION,
	.plugin_description = md_description,
	.ctrl_open = md_ctrl_open,
	.ctrl_close = md_ctrl_close,
	.ctrl_req = md_ctrl_req,
	.ctrl_infos = md_ctrl_infos,
	.ctrl_free = md_ctrl_free
};
