/*
 * tools/lib/vg_setup_for_create.c
 *
 * Copyright (C) 1997 - 2002  Heinz Mauelshagen, Sistina Software
 *
 * March 1997
 * February 1998
 * January,September 2000
 * June 2001
 * January-February 2002
 *
 *
 * This LVM library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This LVM library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this LVM library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 *
 */

/*
 * Changelog
 *
 *    12/02/1998 - changed creation of VG number in vg_setup_for_create()
 *    27/06/1998 - changed lvm_tab_* calling convention
 *    08/02/2000 - use debug_enter()/debug_leave()
 *    24/04/2000 - used lvm_create_uuid() to initialize volume group UUID
 *    15/09/2000 - alligned beginning of pv_uuidlist_in_disk structures
 *                 on sector boundary (Christoph Hellwig <hch@ns.lst.de>)
 *    22/06/2001 - added Andreas Dilger's PE on 4k boundary alignment enhancements
 *    25/06/2001 - Round down pv_size when calculating the number of PEs it can hold
 *                 so that it leaves room for the VGDA.
 *    26/06/2001 - Better fix to make sure there's room for the VGDA
 *    29/01/2002 - removed wrong check for ~64k PE max per PV
 *    06/02/2002 - cope with changed pv_get_size() return cast
 *    17/02/2002 - use pv_get_size_ll() for library compatibility
 *
 */

#include <liblvm.h>

int vg_setup_for_create ( char *vg_name, vg_t *vg, pv_t **pv,
                          int pe_size, ulong max_pv, ulong max_lv) {
   int p = 0;
   int ret = 0;
   uint pe_total = 0;
   long long pv_size;
   uint size;

   debug_enter ( "vg_setup_for_create -- CALLED\n");

   if ( vg_name == NULL || vg == NULL || pv == NULL ||
        pe_size < LVM_MIN_PE_SIZE || pe_size > LVM_MAX_PE_SIZE ||
        pe_size % LVM_MIN_PE_SIZE != 0 ||
        max_pv > ABS_MAX_PV || max_lv > ABS_MAX_LV) {
      ret = -LVM_EPARAM;
      goto vg_setup_for_create_end;
   }

   strcpy ( vg->vg_name, vg_name);
   vg->vg_number = lvm_tab_get_free_vg_number ();
   if ( vg->vg_number < 0 || vg->vg_number > MAX_VG) {
      ret = -LVM_EVG_SETUP_FOR_CREATE_VG_NUMBER;
      goto vg_setup_for_create_end;
   }
   vg->vg_access = VG_READ | VG_WRITE;
   vg->vg_status = VG_EXTENDABLE; /* bitfield */
   vg->lv_max = max_lv;
   vg->lv_cur = 0;
   vg->lv_open = 0;
   vg->pv_max = max_pv;
   vg->pe_size = pe_size;
   vg->vgda = 0;
   vg->pe_allocated = 0;
   vg->pvg_total = 0;
   vg->proc = NULL;
   memset ( vg->vg_uuid, 0, sizeof ( vg->vg_uuid));
   memcpy ( vg->vg_uuid, lvm_create_uuid ( UUID_LEN), sizeof ( vg->vg_uuid));
   memset ( vg->pv, 0, sizeof ( pv_t *) * vg->pv_max);
   memset ( vg->lv, 0, sizeof ( lv_t *) * vg->lv_max);

   /* Walk through physical volumes */
   pe_total = 0;
   for ( p = 0; pv[p] != NULL; p++) {
      pv_t *pp = pv[p];

      /* FIXME: surely we should be comparing (pv_size - VGDA size) ? [JT] */
      if ( pv[p]->pv_size != 0) {
         size = pv[p]->pv_size;
      } else {
         pv_size = pv_get_size_ll ( pp->pv_name, NULL);
         size = pv_size;
         if ( ( pv_size < 0) ||
              ( size / pe_size < LVM_PE_SIZE_PV_SIZE_REL)) {
            ret = -LVM_EVG_SETUP_FOR_CREATE_PV_SIZE_MIN;
            goto vg_setup_for_create_end;
         }
      }
      pp->pv_size = size;

      vg->pv[p] = pp;
      strcpy ( pp->vg_name, vg->vg_name);
      pp->pv_number = p + 1;
      pp->pv_status = 0; /* bitfield */
      pp->pv_allocatable = PV_ALLOCATABLE; /* bitfield */
      pp->pe_size = vg->pe_size;

      pp->pv_on_disk.base = LVM_PV_DISK_BASE;
      pp->pv_on_disk.size = LVM_PV_DISK_SIZE;
      pp->vg_on_disk.base = LVM_VG_DISK_BASE;
      pp->vg_on_disk.size = LVM_VG_DISK_SIZE;
      pp->pv_uuidlist_on_disk.base = LVM_PV_UUIDLIST_DISK_BASE;
      pp->pv_uuidlist_on_disk.size = ( max_pv + 1) * NAME_LEN;

      pp->lv_on_disk.base =
	      round_up(pp->pv_uuidlist_on_disk.base +
		       pp->pv_uuidlist_on_disk.size, LVM_VGDA_ALIGN);
      pp->lv_on_disk.size = (max_lv + 1) * sizeof ( lv_disk_t);

      if(!setup_pe_table(pp)) {
        debug("vg_setup_for_create - unable to setup pe table\n");
        ret = -LVM_EVG_SETUP_FOR_CREATE_PV_SIZE_MIN;
	goto vg_setup_for_create_end;
      }

      if ( ( pp->pe = malloc ( pp->pe_total * sizeof ( pe_disk_t))) == NULL) {
         fprintf ( stderr, "malloc error in %s [line %d]\n",
                           __FILE__, __LINE__);
         ret = -LVM_EVG_SETUP_FOR_CREATE_MALLOC;
         goto vg_setup_for_create_end;
      }
      memset ( pp->pe, 0, pp->pe_total * sizeof ( pe_disk_t));
      pp->pe_allocated = 0;
      pp->pe_stale = 0;
      pe_total += pp->pe_total;
   }
   vg->pv_cur = vg->pv_act = p;
   vg->pe_total = pe_total;

vg_setup_for_create_end:

   debug_leave ( "vg_setup_for_create -- LEAVING with ret: %d\n", ret);
   return ret;
}

int setup_pe_table(pv_t *p)
{
	ulong space, pe_table_size, pe_size;

	p->pe_on_disk.base = round_up(p->lv_on_disk.base + p->lv_on_disk.size,
				      LVM_VGDA_ALIGN);

	/* guess how many pe's we have room for */
	space = p->pv_size - div_up(p->pe_on_disk.base, SECTOR_SIZE);
	p->pe_total = space / p->pe_size;

	/* keep knocking of pe's until we can fit the pe table in */
	for(; p->pe_total; p->pe_total--) {
		/* all sizes in sectors */
		pe_table_size =
			round_up(div_up(p->pe_total * sizeof(pe_disk_t),
					SECTOR_SIZE), LVM_PE_ALIGN);
		pe_size = p->pe_total * p->pe_size;

		/* we want at least a PE's worth of space between the
                   the pe table and the pe's */
		if((pe_table_size + pe_size + p->pe_size) <= space)
			break;
	}

	if(!p->pe_total)
		return 0;

	p->pe_on_disk.size = (pe_table_size + p->pe_size) * SECTOR_SIZE;
	p->pe_start = div_up(p->pe_on_disk.base + p->pe_on_disk.size,
			     SECTOR_SIZE);

	return 1;
}

