/*
 * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
 * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved.
 *
 * This file is part of LVM2.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License v.2.1.
 *
 * You should have received a copy of the GNU Lesser 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 "lib.h"
#include "metadata.h"
#include "disk-rep.h"
#include "lv_alloc.h"
#include "display.h"
#include "segtype.h"

/*
 * After much thought I have decided it is easier,
 * and probably no less efficient, to convert the
 * pe->le map to a full le->pe map, and then
 * process this to get the segments form that
 * we're after.  Any code which goes directly from
 * the pe->le map to segments would be gladly
 * accepted, if it is less complicated than this
 * file.
 */
struct pe_specifier {
	struct physical_volume *pv;
	uint32_t pe;
};

struct lv_map {
	struct logical_volume *lv;
	uint32_t stripes;
	uint32_t stripe_size;
	struct pe_specifier *map;
};

static struct dm_hash_table *_create_lv_maps(struct dm_pool *mem,
					  struct volume_group *vg)
{
	struct dm_hash_table *maps = dm_hash_create(32);
	struct lv_list *ll;
	struct lv_map *lvm;

	if (!maps) {
		log_error("Unable to create hash table for holding "
			  "extent maps.");
		return NULL;
	}

	dm_list_iterate_items(ll, &vg->lvs) {
		if (ll->lv->status & SNAPSHOT)
			continue;

		if (!(lvm = dm_pool_alloc(mem, sizeof(*lvm))))
			goto_bad;

		lvm->lv = ll->lv;
		/*
		 * Alloc 1 extra element, so the loop in _area_length() and
		 * _check_stripe() finds the last map member as noncontinuous.
		 */
		if (!(lvm->map = dm_pool_zalloc(mem, sizeof(*lvm->map)
					     * (ll->lv->le_count + 1))))
			goto_bad;

		if (!dm_hash_insert(maps, ll->lv->name, lvm))
			goto_bad;
	}

	return maps;

      bad:
	dm_hash_destroy(maps);
	return NULL;
}

static int _fill_lv_array(struct lv_map **lvs,
			  struct dm_hash_table *maps, struct disk_list *dl)
{
	struct lvd_list *ll;
	struct lv_map *lvm;

	memset(lvs, 0, sizeof(*lvs) * MAX_LV);

	dm_list_iterate_items(ll, &dl->lvds) {
		if (!(lvm = dm_hash_lookup(maps, strrchr((char *)ll->lvd.lv_name, '/')
					+ 1))) {
			log_error("Physical volume (%s) contains an "
				  "unknown logical volume (%s).",
				dev_name(dl->dev), ll->lvd.lv_name);
			return 0;
		}

		lvm->stripes = ll->lvd.lv_stripes;
		lvm->stripe_size = ll->lvd.lv_stripesize;

		lvs[ll->lvd.lv_number] = lvm;
	}

	return 1;
}

static int _fill_maps(struct dm_hash_table *maps, struct volume_group *vg,
		      struct dm_list *pvds)
{
	struct disk_list *dl;
	struct physical_volume *pv;
	struct lv_map *lvms[MAX_LV], *lvm;
	struct pe_disk *e;
	uint32_t i, lv_num, le;

	dm_list_iterate_items(dl, pvds) {
		if (!(pv = find_pv(vg, dl->dev))) {
			log_error("PV %s not found.", dl->dev->pvid);
			return 0;
		}
		e = dl->extents;

		/* build an array of lv's for this pv */
		if (!_fill_lv_array(lvms, maps, dl))
			return_0;

		for (i = 0; i < dl->pvd.pe_total; i++) {
			lv_num = e[i].lv_num;

			if (lv_num == UNMAPPED_EXTENT)
				continue;

			else {
				lv_num--;
				lvm = lvms[lv_num];

				if (!lvm) {
					log_error("Invalid LV in extent map "
						  "(PV %s, PE %" PRIu32
						  ", LV %" PRIu32
						  ", LE %" PRIu32 ")",
						  dev_name(pv->dev), i,
						  lv_num, e[i].le_num);
					return 0;
				}

				le = e[i].le_num;

				if (le >= lvm->lv->le_count) {
					log_error("logical extent number "
						  "out of bounds");
					return 0;
				}

				if (lvm->map[le].pv) {
					log_error("logical extent (%u) "
						  "already mapped.", le);
					return 0;
				}

				lvm->map[le].pv = pv;
				lvm->map[le].pe = i;
			}
		}
	}

	return 1;
}

static int _check_single_map(struct lv_map *lvm)
{
	uint32_t i;

	for (i = 0; i < lvm->lv->le_count; i++) {
		if (!lvm->map[i].pv) {
			log_error("Logical volume (%s) contains an incomplete "
				  "mapping table.", lvm->lv->name);
			return 0;
		}
	}

	return 1;
}

static int _check_maps_are_complete(struct dm_hash_table *maps)
{
	struct dm_hash_node *n;
	struct lv_map *lvm;

	for (n = dm_hash_get_first(maps); n; n = dm_hash_get_next(maps, n)) {
		lvm = (struct lv_map *) dm_hash_get_data(maps, n);

		if (!_check_single_map(lvm))
			return_0;
	}
	return 1;
}

static uint32_t _area_length(struct lv_map *lvm, uint32_t le)
{
	uint32_t len = 0;

	do
		len++;
	while ((lvm->map[le + len].pv == lvm->map[le].pv) &&
		 (lvm->map[le].pv &&
		  lvm->map[le + len].pe == lvm->map[le].pe + len));

	return len;
}

static int _read_linear(struct cmd_context *cmd, struct lv_map *lvm)
{
	uint32_t le = 0, len;
	struct lv_segment *seg;
	struct segment_type *segtype;

	if (!(segtype = get_segtype_from_string(cmd, "striped")))
		return_0;

	while (le < lvm->lv->le_count) {
		len = _area_length(lvm, le);

		if (!(seg = alloc_lv_segment(segtype, lvm->lv, le, len, 0, 0,
					     NULL, NULL, 1, len, 0, 0, 0, NULL))) {
			log_error("Failed to allocate linear segment.");
			return 0;
		}

		if (!set_lv_segment_area_pv(seg, 0, lvm->map[le].pv,
					    lvm->map[le].pe))
			return_0;

		dm_list_add(&lvm->lv->segments, &seg->list);

		le += seg->len;
	}

	return 1;
}

static int _check_stripe(struct lv_map *lvm, uint32_t area_count,
			 uint32_t area_len, uint32_t base_le,
			 uint32_t total_area_len)
{
	uint32_t st;

	/*
	 * Is the next physical extent in every stripe adjacent to the last?
	 */
	for (st = 0; st < area_count; st++)
		if ((lvm->map[base_le + st * total_area_len + area_len].pv !=
		     lvm->map[base_le + st * total_area_len].pv) ||
		    (lvm->map[base_le + st * total_area_len].pv &&
		     lvm->map[base_le + st * total_area_len + area_len].pe !=
		     lvm->map[base_le + st * total_area_len].pe + area_len))
			return 0;

	return 1;
}

static int _read_stripes(struct cmd_context *cmd, struct lv_map *lvm)
{
	uint32_t st, first_area_le = 0, total_area_len;
	uint32_t area_len;
	struct lv_segment *seg;
	struct segment_type *segtype;

	/*
	 * Work out overall striped length
	 */
	if (lvm->lv->le_count % lvm->stripes) {
		log_error("Number of stripes (%u) incompatible "
			  "with logical extent count (%u) for %s",
			  lvm->stripes, lvm->lv->le_count, lvm->lv->name);
	}

	total_area_len = lvm->lv->le_count / lvm->stripes;

	if (!(segtype = get_segtype_from_string(cmd, "striped")))
		return_0;

	while (first_area_le < total_area_len) {
		area_len = 1;

		/*
		 * Find how many extents are contiguous in all stripes
		 * and so can form part of this segment
		 */
		while (_check_stripe(lvm, lvm->stripes,
				     area_len, first_area_le, total_area_len))
			area_len++;

		if (!(seg = alloc_lv_segment(segtype, lvm->lv,
					     lvm->stripes * first_area_le,
					     lvm->stripes * area_len,
					     0, lvm->stripe_size, NULL, NULL,
					     lvm->stripes,
					     area_len, 0, 0, 0, NULL))) {
			log_error("Failed to allocate striped segment.");
			return 0;
		}

		/*
		 * Set up start positions of each stripe in this segment
		 */
		for (st = 0; st < seg->area_count; st++)
			if (!set_lv_segment_area_pv(seg, st,
			      lvm->map[first_area_le + st * total_area_len].pv,
			      lvm->map[first_area_le + st * total_area_len].pe))
				return_0;

		dm_list_add(&lvm->lv->segments, &seg->list);

		first_area_le += area_len;
	}

	return 1;
}

static int _build_segments(struct cmd_context *cmd, struct lv_map *lvm)
{
	return (lvm->stripes > 1 ? _read_stripes(cmd, lvm) :
		_read_linear(cmd, lvm));
}

static int _build_all_segments(struct cmd_context *cmd, struct dm_hash_table *maps)
{
	struct dm_hash_node *n;
	struct lv_map *lvm;

	for (n = dm_hash_get_first(maps); n; n = dm_hash_get_next(maps, n)) {
		lvm = (struct lv_map *) dm_hash_get_data(maps, n);
		if (!_build_segments(cmd, lvm))
			return_0;
	}

	return 1;
}

int import_extents(struct cmd_context *cmd, struct volume_group *vg,
		   struct dm_list *pvds)
{
	int r = 0;
	struct dm_pool *scratch = dm_pool_create("lvm1 import_extents", 10 * 1024);
	struct dm_hash_table *maps;

	if (!scratch)
		return_0;

	if (!(maps = _create_lv_maps(scratch, vg))) {
		log_error("Couldn't allocate logical volume maps.");
		goto out;
	}

	if (!_fill_maps(maps, vg, pvds)) {
		log_error("Couldn't fill logical volume maps.");
		goto out;
	}

	if (!_check_maps_are_complete(maps) && !(vg->status & PARTIAL_VG))
		goto_out;

	if (!_build_all_segments(cmd, maps)) {
		log_error("Couldn't build extent segments.");
		goto out;
	}
	r = 1;

      out:
	if (maps)
		dm_hash_destroy(maps);
	dm_pool_destroy(scratch);
	return r;
}
