/*
 * tools/lib/pv_release_pe.c
 *
 * Copyright (C) 1997 - 2000  Heinz Mauelshagen, Sistina Software
 *
 * March 1997
 * July  1998
 * January,July,September 1999
 * January 2000
 *
 *
 * 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
 *
 *    07/07/1998 - added read/write statistic init
 *    21/01/1999 - fixed contiguous logical volume extension bug
 *    05/07/1999 - fixed striped logical volume shrink bug
 *    29/10/1999 - fixed possible free() bug
 *    08/02/2000 - use debug_enter()/debug_leave()
 *
 */

#include <liblvm.h>

int pv_release_pe ( vg_t *vg, pe_disk_t *lv_pe, uint *pe, uint stripes) {
   int l = 0;
   int pv_num = 0;
   int ret = 0;
   uint dest = 0;
   uint i = 0;
   uint j = 0;
   uint length = 0;
   uint np = 0;
   uint offset = 0;
   uint pe_index = 0;
   uint source = 0;
   uint p = 0;
   pe_t *lv_current_pe_sav = NULL;

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

   if ( vg == NULL || lv_pe == NULL || lv_pe->lv_num > vg->lv_max ||
        pe == NULL || *pe == 0) {
      ret = -LVM_EPARAM;
      goto pv_release_pe_end;
   }

   l = lv_pe->lv_num - 1;
   if ( vg->lv[l]->lv_allocated_le % stripes != 0) {
      ret = -LVM_ESIZE;
      goto pv_release_pe_end;
   }
   p = vg->lv[l]->lv_allocated_le - 1;
   np = 0;

   /* linear mode */
   if ( stripes < 2) {
      while ( p >= 0 && np < *pe) {
         for ( pv_num = 0; pv_num < vg->pv_cur; pv_num++) {
            if ( vg->pv[pv_num]->pv_dev == vg->lv[l]->lv_current_pe[p].dev)
               break;
         }
         if ( pv_num == vg->pv_cur) {
            ret = -LVM_EPV_RELEASE_PE_NO_PV;
            goto pv_release_pe_end;
         }
         pe_index = ( vg->lv[l]->lv_current_pe[p].pe - 
                      LVM_DISK_SIZE ( vg->pv[pv_num]) /
                      SECTOR_SIZE) /
                      vg->pe_size;
         debug ( "pv_release_pe -- pv_name: %s  pe: %lu  sector: %lu\n",
                  vg->pv[pv_num]->pv_name,
                  pe_index,
                  vg->lv[l]->lv_current_pe[p].pe);
         vg->pv[pv_num]->pe[pe_index].lv_num = \
         vg->pv[pv_num]->pe[pe_index].le_num = 0;
         vg->pv[pv_num]->pe_allocated--;
         vg->lv[l]->lv_current_le--;
         vg->lv[l]->lv_allocated_le--;
         np++;
         if ( lv_check_on_pv ( vg->pv[pv_num], lv_pe->lv_num) != TRUE)
            vg->pv[pv_num]->lv_cur--;
         p--;
      }

   /* striped mode */
   } else {
      while ( p >= 0 && np < *pe) {
         offset = vg->lv[l]->lv_allocated_le / stripes;
         for ( i = 0; i < *pe / stripes; i++) {
            for ( j = 1; j <= stripes; j++) {
               p = j * offset - 1 - i;
               for ( pv_num = 0; pv_num < vg->pv_cur; pv_num++) {
                  if ( vg->pv[pv_num]->pv_dev ==
                          vg->lv[l]->lv_current_pe[p].dev)
                     break;
               }
               pe_index = ( vg->lv[l]->lv_current_pe[p].pe - 
                            LVM_PE_DISK_OFFSET ( 0, vg->pv[pv_num])) /
                            vg->pe_size;
               vg->pv[pv_num]->pe[pe_index].lv_num = \
               vg->pv[pv_num]->pe[pe_index].le_num = 0;
               vg->pv[pv_num]->pe_allocated--;
               vg->lv[l]->lv_current_le--;
               vg->lv[l]->lv_allocated_le--;
               np++;
               if ( lv_check_on_pv ( vg->pv[pv_num], lv_pe->lv_num) != TRUE)
                  vg->pv[pv_num]->lv_cur--;
               p--;
            }
         }
         length = offset - *pe / stripes;
         for ( i = 1; i < stripes; i++) {
            source = i * offset;
            dest = source - i * ( *pe / stripes);
            for ( j = 0; j < length; j++) {
               memcpy ( &vg->lv[l]->lv_current_pe[dest+j],
                        &vg->lv[l]->lv_current_pe[source+j],
                        sizeof ( vg->lv[l]->lv_current_pe[source+j]));
               pv_num = pv_get_index_by_kdev_t (
                           vg,
                           vg->lv[l]->lv_current_pe[dest+j].dev
                        );
               vg->pv[pv_num]->pe[(vg->lv[l]->lv_current_pe[dest+j].pe-LVM_PE_DISK_OFFSET( 0, vg->pv[pv_num]))/vg->pe_size].le_num = dest+j;
            }
         }
         lv_current_pe_sav = vg->lv[l]->lv_current_pe;
         if ( ( vg->lv[l]->lv_current_pe =
                realloc ( vg->lv[l]->lv_current_pe,
                          stripes * length *
                          sizeof ( pe_t))) == NULL) {
            free ( lv_current_pe_sav);
            fprintf ( stderr, "realloc error in %s [line %d]\n",
                              __FILE__, __LINE__);
            ret = -LVM_EPV_RELEASE_PE_REALLOC;
            goto pv_release_pe_end;
         } else lv_current_pe_sav = NULL;
      }
   }

   *pe -= np;

pv_release_pe_end:

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