/*
 * Copyright (C) 2006 IBM, all rights reserved.
 * Written by Darrick Wong <djwong@us.ibm.com>,
 * James Simshaw <simshawj@us.ibm.com>, and
 * Adam DiCarlo <bikko@us.ibm.com>
 *
 * Copyright (C) 2006-2008 Heinz Mauelshagen, Red Hat GmbH
 * 		           All rights reserved
 *
 * Copyright (C) 2007,2009   Intel Corporation. All rights reserved.
 * November, 2007 - additions for Create, Delete, Rebuild & Raid 10.
 * March, 2008 - additions for hot-spare check
 * August, 2008 - check before Rebuild
 * January, 2009 - additions for Activation, Rebuild check
 * May, 2009 - fix for metadata update during rebuild
 *
 * See file LICENSE at the top of this source tree for license information.
 */
#include "internal.h"
int dso = 0;

#define	add_to_log(entry, log)	\
	list_add_tail(&(entry)->changes, &(log));

#define	LIB_NAME_LENGTH	255

static inline int
alloc_entry(struct change **entry, struct lib_context *lc)
{
	return (*entry = dbg_malloc(sizeof(*entry))) ? 0 : -ENOMEM;
}

static int
nuke_spare(struct lib_context *lc, struct raid_dev *rd)
{
	printf("Nuking Spare\n");
	list_del_init(&rd->devs);
	return 0;
}

int
dso_end_rebuild(struct lib_context *lc, int arg)
{
	struct raid_set *sub_rs;
	const char *vol_name = lc->options[LC_REBUILD_SET].arg.str;

	sub_rs = find_set(lc, NULL, vol_name, FIND_ALL);
	if (sub_rs) {
		struct raid_set *rs = NULL;

		rs = find_group(lc, sub_rs);
		if ((rs) && (S_OK(sub_rs->status) || S_NOSYNC(sub_rs->status))) {
			struct raid_dev *check_rd = RD_RS(sub_rs);
			enum status state = s_ok;

			if (check_rd->fmt->metadata_handler)
				state = check_rd->fmt->metadata_handler(lc,
									GET_REBUILD_STATE,
									NULL,
									(void *)
									sub_rs);

			if (state != s_nosync) {
				/* cannot rebuild */
				log_print(lc,
					  "Volume \"%s\" is not in rebuild state (current: %u)\n",
					  sub_rs->name, state);
				return 1;
			}

			if (check_rd->fmt->metadata_handler)
				check_rd->fmt->metadata_handler(lc,
								UPDATE_REBUILD_STATE,
								NULL,
								(void *) rs);
		} else {
			log_print(lc,
				  "Volume \"%s\" is not in rebuild state \n",
				  vol_name);
			return 1;
		}
	} else
		log_print(lc, "raid volume \"%s\" not found\n", vol_name);

	return 0;
}

int rebuild_config_raidset(struct lib_context *lc, struct raid_set *rs);

void
show_raid_stack(struct lib_context *lc)
{
	struct raid_set *_rs;
	log_dbg(lc, "RM: Discovered raid sets:");
	list_for_each_entry(_rs, LC_RS(lc), list) {
		struct raid_dev *_rd;
		struct raid_set *_rss;

		log_dbg(lc, "RM: GROUP name: \"%s\"", _rs->name);

		list_for_each_entry(_rd, &_rs->devs, devs) {
			log_dbg(lc, "RM: GROUP_DISK name: \"%s\"",
				(_rd->di) ? _rd->di->path : "UNKNOWN");
		}

		list_for_each_entry(_rss, &_rs->sets, list) {
			struct raid_dev *_rsd;
			struct raid_set *_rsss;

			log_dbg(lc, "RM:   SUPERSET name: \"%s\"", _rss->name);

			list_for_each_entry(_rsd, &_rss->devs, devs) {
				log_dbg(lc, "RM:   SUPERSET_DISK name: \"%s\"",
					(_rsd->di) ? _rsd->
					di->path : "UNKNOWN");
			}

			list_for_each_entry(_rsss, &_rss->sets, list) {
				struct raid_dev *_rssd;
				log_dbg(lc, "RM:     SUBSET name: \"%s\"",
					_rsss->name);
				list_for_each_entry(_rssd, &_rsss->devs, devs) {
					log_dbg(lc,
						"RM:     SUBSET_DISK name: \"%s\"",
						(_rssd->di) ? _rssd->
						di->path : "UNKNOWN");
				}
			}
		}
	}
}

/* Add a device to a RAID1 set and start the resync */
static int
add_dev_to_raid(struct lib_context *lc, struct raid_set *rs,
		struct raid_dev *rd)
{
	int ret = 0;
	const char *vol_name = lc->options[LC_REBUILD_SET].arg.str;
	struct raid_set *sub_rs, *crs;
	struct raid_dev *check_rd;
	LIST_HEAD(log);		/* playback log */

	sub_rs = find_set(lc, NULL, vol_name, FIND_ALL);
	check_rd = list_entry(rs->devs.next, typeof(*rd), devs);

	if (rd) {
		struct handler_info info;

		if (check_rd->fmt->create) {
			struct raid_dev *tmp;
			if ((ret = check_rd->fmt->create(lc, rs))) {

				list_for_each_entry(tmp, &rs->devs, devs) {
					write_dev(lc, tmp, 0);
				}
			} else {
				log_print(lc, "metadata fmt update failed\n");
				goto err;
			}
		} else {
			log_print(lc, "create failed fmt handler missing\n");
			goto err;
		}

		if (lc->options[LC_REBUILD_SET].opt) {
			if (check_rd->fmt->metadata_handler) {
				if (!check_rd->
				    fmt->metadata_handler(lc,
							  GET_REBUILD_DRIVE_NO,
							  &info,
							  (void *) sub_rs)) {
					LOG_ERR(lc, 0,
						"can't get rebuild drive !");
				}
			}
		}

		if (info.data.i32 != -1) {
			struct raid_dev *before_rd, *tmp_rd;
			int idx = 0;
			list_for_each_entry_safe(before_rd, tmp_rd,
						 &sub_rs->devs, devs) {
				if ((idx == info.data.i32) &&
				    &rd->devs != &before_rd->devs) {
					list_del(&rd->devs)
						list_add_tail(&rd->devs,
							      &before_rd->devs);
					break;
				}

				idx++;
			}
		}

		show_raid_stack(lc);
		log_dbg(lc, "RM: REBUILD drivie #: \"%d\"", info.data);
		show_raid_stack(lc);
	}

	/* Reconfigure device mapper */
	// FIXME: is nosync enough? rs->status |= s_inconsistent;
	rs->status = s_ok;
	if ((sub_rs = find_set(lc, NULL, vol_name, FIND_ALL))) {
		sub_rs->status = s_ok;

		list_for_each_entry(crs, &sub_rs->sets, list)
			crs->status = s_ok;
	}

	change_set(lc, A_ACTIVATE, rs);
	rs->status |= s_nosync;

	if ((sub_rs = find_set(lc, NULL, vol_name, FIND_ALL))) {
		sub_rs->status |= s_nosync;
		list_for_each_entry(crs, &sub_rs->sets, list)
			crs->status |= s_nosync;
	}

	ret = change_set(lc, A_RELOAD, rs);
	// FIXME: might need this later: change_set(lc, A_DEACTIVATE,rs);
	if (!ret)
		goto err;

#ifdef DMRAID_AUTOREGISTER
	/* if call is from dmraid (not from dso) */
	if (!dso) {
		int pending;
		char lib_name[LIB_NAME_LENGTH] = { 0 };
		struct dmraid_format *fmt = get_format(sub_rs);

#ifdef DMRAID_LED
		struct raid_dev *_rd;

		list_for_each_entry(_rd, &sub_rs->devs, devs)
				    led(strrchr(_rd->di->path, '/') + 1,
					LED_REBUILD);
#endif
		/*
		 * If raid is registered with libdmraid-events 
		 * leave NOSYNC state, else update
		 * metadata on disks to OK state.
		 */
		/* Create lib-events library name */
		if (fmt->name) {
			strncpy(lib_name, "libdmraid-events-", LIB_NAME_LENGTH);
			strncat(lib_name, fmt->name,
				LIB_NAME_LENGTH-strlen(fmt->name)-3);
			strncat(lib_name, ".so", 3);
		} else
			goto err;

		/* Check registration */
		if (!dm_monitored_events(&pending, sub_rs->name, lib_name)) {
			/* If NOT registered update metadata to OK state. */
			if (check_rd->fmt->metadata_handler)
				check_rd->fmt->metadata_handler(lc, UPDATE_REBUILD_STATE, NULL, (void *) rs);
		}
	}
#endif

	/* End transaction */
	end_log(lc, &log);
	return 0;

err:
	revert_log(lc, &log);
	return ret;
}

struct dev_info *find_disk(struct lib_context *lc, char *dp);

/* check if disk is under a raid set */
int
check_busy_disk(struct lib_context *lc, struct raid_dev *check_rd)
{
	struct raid_dev *rd;

	if (!check_rd)
		return 0;

	if (!check_rd->di)
		return 0;

	list_for_each_entry(rd, LC_RD(lc), list) {
		if (rd->di == check_rd->di)
			return 0;
	}

	return 1;
}

int
add_dev_to_array(struct lib_context *lc, struct raid_set *rs,
		 uint build_metadata, struct raid_dev *hot_spare_rd)
{
	const char *set_name = lc->options[LC_REBUILD_SET].arg.str;
	struct raid_dev *rd = NULL;
	struct raid_set *sub_rs;

	if ((hot_spare_rd) || (build_metadata)) {
		struct raid_dev tmp_rd;
		struct raid_dev *new_rd = hot_spare_rd;
		enum type type = t_raid1;

		sub_rs = find_set(lc, NULL, set_name, FIND_ALL);
		if (sub_rs == NULL) {
			log_print(lc, "Volume \"%s\" not found\n", set_name);
			return 1;
		}


		type = sub_rs->type;

		if (new_rd == NULL) {
			const char *disk_name =
				lc->options[LC_REBUILD_DISK].arg.str;

			new_rd = &tmp_rd;
			/* for non-hot-spare */
			if (!(new_rd->di = find_disk(lc, (char *) disk_name)))
				LOG_ERR(lc, 0, "failed to find disk %s",
					disk_name);

			/* check if disk is a member of another raid set */
			if (!(check_busy_disk(lc, new_rd)))
				LOG_ERR(lc, 0,
					"disk %s cannot be used to rebuilding",
					disk_name);

			new_rd->fmt = get_format(rs);
		}

		/* add a rd to group raid set */
		if ((rd = alloc_raid_dev(lc, "rebuild")) == NULL)
			LOG_ERR(lc, 1,
				"failed to allocate space for a raid_dev");

		memset(rd, 0, sizeof(*rd));
		rd->name = NULL;

		rd->di = new_rd->di;
		rd->fmt = new_rd->fmt;

		rd->status = s_init;
		rd->type = type;
		rd->offset = 0;
		rd->sectors = 0;

		list_add_tail(&rd->list, LC_RD(lc));
		list_add_tail(&rd->devs, &rs->devs);

		/* add a spare to raid set */
		sub_rs = find_set(lc, NULL, set_name, FIND_ALL);

		if (!(rd = alloc_raid_dev(lc, __func__)))
			LOG_ERR(lc, 1,
				"failed to allocate space for a raid_dev");

		rd->name = NULL;
		rd->di = new_rd->di;
		rd->fmt = new_rd->fmt;
		rd->status = s_init;
		rd->type = type;
		rd->offset = 0;
		rd->sectors = 0;
		list_add_tail(&rd->devs, &sub_rs->devs);
		sub_rs->total_devs++;
	}

	add_dev_to_raid(lc, rs, rd);
	return 0;
}

/* Remove a disk from a raid1 */
static int
del_dev_in_raid1(struct lib_context *lc, struct raid_set *rs,
		 struct raid_dev *rd)
{
	int ret;
	struct raid_dev *tmp;
	struct change *entry;
	LIST_HEAD(log);		/* Playback log */

	/* Remove device from the raid set */
	ret = alloc_entry(&entry, lc);
	if (ret)
		goto err;

	entry->type = DELETE_FROM_SET;
	entry->rs = rs;
	entry->rd = rd;
	add_to_log(entry, log);
	list_del_init(&rd->devs);
	rd->type = t_spare;

	/* Check that this is a sane configuration */
	list_for_each_entry(tmp, &rs->devs, devs) {
		ret = tmp->fmt->check(lc, rs);
		if (ret)
			goto err;
	}

	/* Write the metadata of the drive we're removing _first_ */
	ret = alloc_entry(&entry, lc);
	if (ret)
		goto err;

	entry->type = WRITE_METADATA;
	entry->rd = rd;
	add_to_log(entry, log);
	ret = write_dev(lc, rd, 0);
	if (!ret)
		goto err;

	/* Write metadatas of every device in the set */
	list_for_each_entry(tmp, &rs->devs, devs) {
		if (tmp == rd)
			continue;

		ret = alloc_entry(&entry, lc);
		if (ret)
			goto err;

		entry->type = WRITE_METADATA;
		entry->rd = tmp;
		add_to_log(entry, log);
		ret = write_dev(lc, tmp, 0);
		if (!ret)
			goto err;
	}

	/* Reconfigure device mapper */
	rs->status |= s_inconsistent;
	rs->status |= s_nosync;
	ret = change_set(lc, A_RELOAD, rs);
	if (!ret)
		goto err;

	/* End transaction */
	end_log(lc, &log);
	return 0;

err:
	revert_log(lc, &log);
	return ret;
}

/* Corelate type and function to handle addition/removel of RAID device */
struct handler {
	enum change_type type;
	int (*func) (struct lib_context * lc, struct raid_set * rs,
		     struct raid_dev * rd);
};

/* Call the function to handle addition/removal of a RAID device */
static int
handle_dev(struct lib_context *lc, struct handler *h,
	   struct raid_set *rs, struct raid_dev *rd)
{
	do {
		if (h->type == rs->type)
			return h->func(lc, rs, rd);
	} while ((++h)->type != t_undef);

	LOG_ERR(lc, -ENOENT, "%s: no handler for %x", __func__, rs->type);
}

/* Add a disk to an array. */
int
add_dev_to_set(struct lib_context *lc, struct raid_set *rs, struct raid_dev *rd)
{
	struct handler handlers[] = {
		{t_raid1, add_dev_to_raid},
		{t_undef, NULL},
	};

	if (T_SPARE(rd))
		nuke_spare(lc, rd);
	else if (!list_empty(&rd->devs))
		LOG_ERR(lc, -EBUSY, "%s: disk already in another set!",
			__func__);

	if (T_GROUP(rd))
		LOG_ERR(lc, -EISDIR,
			"%s: can't add a group raid_dev to a raid_set.",
			__func__);

	return handle_dev(lc, handlers, rs, rd);
}

/* Remove a disk from an array */
int
del_dev_in_set(struct lib_context *lc, struct raid_set *rs, struct raid_dev *rd)
{
	struct handler handlers[] = {
		{t_raid1, del_dev_in_raid1},
		{t_undef, NULL},
	};

	if (list_empty(&rd->devs))
		LOG_ERR(lc, -EBUSY, "%s: disk is not in a set!", __func__);

	/* FIXME: Not sure if this is true. */
	if (T_GROUP(rd))
		LOG_ERR(lc, -EISDIR,
			"%s: can't remove a group raid_dev from a raid_set.",
			__func__);

	return handle_dev(lc, handlers, rs, rd);
}

/*
 * Find group of raid_set to which sub_rs belongs.
 *
 * Serves one level stacked raids.
 */
struct raid_set *
find_group(struct lib_context *lc, struct raid_set *sub_rs)
{
	struct raid_set *tmp = NULL, *r = NULL, *r2 = NULL;

	list_for_each_entry(tmp, LC_RS(lc), list) {
		if (T_GROUP(tmp)) {
			list_for_each_entry(r, &tmp->sets, list) {
				if (r == sub_rs)
					return tmp;
				else if (SETS(r)) {
					list_for_each_entry(r2, &r->sets,
							    list) {
						if (r2 == sub_rs)
							return tmp;
					}
				}
			}
		}
	}

	/* Group not found. */
	return NULL;
}

struct raid_dev *find_spare(struct lib_context *lc, struct raid_set *sub_rs,
			    struct raid_set **spare_set);

int
_rebuild_raidset(struct lib_context *lc, struct raid_set *sub_rs,
		 char *set_name)
{
	struct raid_set *spare_set = NULL, *rs = NULL;
	struct raid_dev *rd = NULL;
	int driveRebuild = 1;

	rs = find_group(lc, sub_rs);

	/* raid 0 cannot be rebuild - exit */
	if (T_RAID0(sub_rs) && (!SETS(sub_rs))) {
		log_print(lc, "Rebuild: raid0 cannot be rebuild\n");
		return 1;
	}

	/* FIXME - work-around for status reporting */
	if (S_BROKEN(sub_rs->status) ||
	    S_INCONSISTENT(sub_rs->status)) {
		if (lc->options[LC_REBUILD_DISK].opt == 0) {
			/* find the spare drive */
			if ((rd = find_spare(lc, sub_rs, &spare_set)) == NULL) {
				log_print(lc,
					  "Rebuild: a hot-spare drive not found for a volume: \"%s\"."
					  " Need a drive to rebuild a volume.\n",
					  sub_rs->name);
				return 1;
			}

		}
	} else if (S_OK(sub_rs->status)) {
		struct raid_dev *check_rd = RD_RS(sub_rs);
		enum status state = s_ok;

		if (check_rd && (check_rd->fmt->metadata_handler))
			state = check_rd->fmt->metadata_handler(lc, GET_REBUILD_STATE, NULL, (void *) sub_rs);

		if (state != s_nosync) {
			/* cannot rebuild */
			log_print(lc,
				  "Volume \"%s\" is not in rebuild state (current: %u)",
				  sub_rs->name, state);
			log_print(lc,
				  "Rebuild: cannot rebuild from current state!\n");
			return 1;
		}

		driveRebuild = 0;
	} else if (!(S_NOSYNC(sub_rs->status))) {
		/* cannot rebuild */
		log_print(lc, "Rebuild: cannot rebuild from current state!\n");
		return 1;
	}


	sub_rs->status = s_nosync;
	rs->status = s_nosync;

	/* set the name for rebuild set (function down the path are using this variable to
	 * retrive the raid set that is rebuild 
	 */


	dbg_free((char *) lc->options[LC_REBUILD_SET].arg.str);
	lc->options[LC_REBUILD_SET].arg.str =
		(const char *) dbg_malloc(strlen(sub_rs->name) + 1);
	strcpy((char *) lc->options[LC_REBUILD_SET].arg.str, sub_rs->name);

	if (!(add_dev_to_array(lc, rs,
			       (driveRebuild
				&& lc->options[LC_REBUILD_DISK].opt)
			       || rd, rd))) {
		log_dbg(lc, "rebuild: raid \"%s\" rebuild finished\n",
                set_name);

		/*
		 * Special case for degraded raid5 array, 
		 * activated with error target device.
		 */
		delete_error_target(lc, sub_rs);
	} else {
		/* error - raid failed to be rebuilded */
		log_print(lc, "Rebuild: raid \"%s\" rebuild failed\n",
			  set_name);
		return 1;
	}

	return 0;
}

/*
 * Before rebuilding the set, we should check
 * that the metadata handler allows it.
 */
static int check_allow_rebuild(struct lib_context *lc, struct raid_set *rs,
			       char *rs_name)
{
	struct raid_dev *rd = list_entry(rs->devs.next, typeof(*rd), devs);

	if (!rd->fmt->metadata_handler ||
	     rd->fmt->metadata_handler(lc, ALLOW_REBUILD, NULL, rs))
		return _rebuild_raidset(lc, rs, rs_name);
	else
		LOG_ERR(lc, 0, "Can't rebuild RAID set \"%s\"", rs_name);
}

int
rebuild_raidset(struct lib_context *lc, char *rs_name)
{
	int ret = 0;
	struct raid_set *sub_rs;

	sub_rs = find_set(lc, NULL, rs_name, FIND_ALL);
	if (!sub_rs)
		LOG_PRINT(lc, 0, "raid volume \"%s\" not found\n", rs_name);

	/*
	 * For stacked subsets go throu the stack to retrive the subsets that:
	 * - do not contain subsets
	 * - are eligible for rebuild 
	 */
	if (SETS(sub_rs)) {
		int i;
		/*
		 * Ordered table of states: causes that sub-rs are rebuilt
		 * is order of state that are present in this table.
		 */
		enum status state_tbl[] = {
			s_ok,
			s_nosync,
			s_broken | s_inconsistent,
			/* TBD change to s_inconsisten 
			   when the states are reported 
			  in right way */
		};
		struct raid_set *rs;

		/* check sub-set that are in Ok state */
		for (i = 0; i < ARRAY_SIZE(state_tbl); i++) {
			/*
			 * Check for all subsets that
			 * have state equal state_tbl[i].
			 */
			list_for_each_entry(rs, &sub_rs->sets, list) {
				if (rs->status & state_tbl[i])
					/*
					 * Before rebuilding the set, 
					 * we should check that the
					 * metadata handler allows it.
					 */
					ret |= check_allow_rebuild(lc, rs,
								   rs_name);
			}
		}
	} else
		ret |= check_allow_rebuild(lc, sub_rs, rs_name);

	return ret;
}


int
write_set_spare(struct lib_context *lc, void *v)
{
	int ret = 1;
	struct raid_set *r, *rs = v;
	struct raid_dev *rd;

	/* Decend hierarchy */
	list_for_each_entry(r, &rs->sets, list) {
		/*
		 * FIXME: does it make sense to try the rest of the subset
		 *        in case we fail writing one ?
		 */
		if (!write_set_spare(lc, (void *) r))
			log_err(lc, "writing RAID subset \"%s\", continuing",
				r->name);
	}

	if (!T_GROUP(rs)) {
		/* Write metadata to the RAID devices of a set */
		list_for_each_entry(rd, &rs->devs, devs) {
			/*
			 * FIXME: does it make sense to try the rest of the
			 *        devices in case we fail writing one ?
			 */
			if (!write_dev(lc, rd, 0)) {
				log_err(lc, "writing RAID device \"%s\", "
					    "continuing",
					rd->di->path);
				ret = 0;
			}
		}
	}

	return ret;
}

static int
add_spare_dev_to_raid(struct lib_context *lc, struct raid_set *rs)
{
	int ret = 0;
	struct dmraid_format *fmt_hand;


	/* find format handler */
	fmt_hand = get_format(rs);

	if (!(fmt_hand->create)) {
		LOG_ERR(lc, 0,
			"metadata creation is not supported in \"%s\" format",
			fmt_hand->name);
	} else {
		if ((ret = fmt_hand->create(lc, rs)))
			ret = write_set_spare(lc, rs);

		if (!ret)
			log_print(lc, "metadata fmt update failed\n");
	}

	return ret;
}

int
add_spare_dev_to_array(struct lib_context *lc, struct raid_set *rs)
{
	struct dev_info *di;
	struct dmraid_format *fmt_hand;
	struct raid_dev *rd;
	struct raid_set *rs_sub;

	const char *disk_name = lc->options[LC_REBUILD_DISK].arg.str;


	/* find format handler */
	fmt_hand = get_format(rs);

	/* add a spare rs to raid set */
	if (!(rs_sub = alloc_raid_set(lc, "rebuild")))
		return 0;

	rs_sub->name = NULL;
	rs_sub->size = 0;
	rs_sub->stride = 0;
	rs_sub->type = t_spare;
	rs_sub->flags = 0;
	rs_sub->status = s_init;
	list_add_tail(&rs_sub->list, &rs->sets);

	/* Find disk by name. */
	if (!(di = find_disk(lc, (char *) disk_name)))
		LOG_ERR(lc, 0, "failed to find disk %s", disk_name);

	/* Add a rd to group raid set. */
	if ((rd = alloc_raid_dev(lc, "rebuild")) == NULL)
		LOG_ERR(lc, 0, "failed to allocate space for a raid_dev");

	rd->name = NULL;
	rd->di = di;
	rd->fmt = fmt_hand;
	rd->status = s_init;
	rd->type = t_spare;
	rd->offset = 0;
	rd->sectors = 0;

	/* add dev to lc list and to group rs */
	list_add_tail(&rd->list, LC_RD(lc));
	list_add_tail(&rd->devs, &rs->devs);

	if (!(rd = alloc_raid_dev(lc, "rebuild")))
		LOG_ERR(lc, 0, "failed to allocate space for a raid_dev");

	rd->name = NULL;
	rd->di = di;
	rd->fmt = fmt_hand;
	rd->status = s_init;
	rd->type = t_spare;
	rd->offset = 0;
	rd->sectors = 0;
	list_add_tail(&rd->devs, &rs_sub->devs);
	return add_spare_dev_to_raid(lc, rs);
}


/* Add a disk to raid set as spare disk. */
int
hot_spare_add(struct lib_context *lc, struct raid_set *rs)
{
	const char *vol_name = lc->options[LC_HOT_SPARE_SET].arg.str;
	int ret_func;
	struct dmraid_format *fmt_hand;


	if (!(!OPT_FORMAT(lc) &&
	      OPT_REBUILD_DISK(lc) &&
	      OPT_HOT_SPARE_SET(lc)))
		return 0;

	if (!(fmt_hand = get_format(rs)))
		LOG_ERR(lc, 0, "unknown metadata format");

	if (!(fmt_hand->metadata_handler))
		LOG_ERR(lc, 0,
			"metadata_handler() is not supported in \"%s\" format",
			fmt_hand->name);

	ret_func = fmt_hand->metadata_handler(lc, CHECK_HOT_SPARE, NULL,
					      (void *) rs);
	if (!ret_func)
		LOG_ERR(lc, 0,
			"hot-spare cannot be added to existing raid "
			"set \"%s\" in \"%s\" format",
			vol_name, fmt_hand->name);

	return add_spare_dev_to_array(lc, rs);
}
