/*
 * tools/lib/pv_get_size.c
 *
 * Copyright (C) 1997 - 1998  Heinz Mauelshagen, Germany
 *
 * March,October 1997
 * May 1998
 * January,March,June,July,September 1999
 *
 *
 * 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/10/1997 - added extended partiton sizing
 *    07/11/1997 - corrected deadlock looping through partition tables
 *    04/05/1998 - added support for non partitioned disks
 *    23/01/1999 - added devfs (only namespace) support
 *    17/03/1999 - minor corrections
 *    22/06/1999 - added recognition of Win98 extended partition
 *                 by Steve Brueggeman
 *    05/07/1999 - fixed invalid partition table return in case of whole disk
 *    22/09/1999 - enhanced to better support disks with empty partition table
 *    15/01/2000 - avoid partition checks on alpha to make it at least working,
 *		   Andrea Arcangeli <andrea@suse.de>
 *
 */

#include <liblvm.h>


#ifndef __alpha__
int pv_get_size ( char *dev_name, struct partition *part_ptr) {
   int i = 0;
   int dir_cache_count = 0;
   int extended_flag = 0;
   int first = 0;
   int part_i = 0;
   int part_i_tmp = 0;
   int pv_handle = -1;
   int ret = 0;
   int this_index = -1;
   static char buffer[SECTOR_SIZE];
   loff_t offset = 0;
   loff_t extended_offset = 0;
   struct partition *part = ( struct partition *) ( buffer + 0x1be);
   unsigned short *s_buffer = ( unsigned short *) buffer;
   char disk_dev_name[NAME_LEN] = { 0, };
   dir_cache_t *dir_cache = NULL;

#ifdef DEBUG
   debug ( "pv_get_size -- CALLED with %s and %X\n",
             dev_name, ( uint) part_ptr);
#endif

   if ( dev_name == NULL ||
        pv_check_name ( dev_name) < 0) return -LVM_EPARAM;

   part_i = pv_check_part ( dev_name);
   if ( part_i < 0) part_i = 0;


   /* Figure out first st_rdev in case of SCSI, IDE etc. */
   if ( ( dir_cache_count = lvm_dir_cache ( &dir_cache)) < 1) {
      ret = -LVM_EPV_GET_SIZE_LVM_DIR_CACHE;
      goto pv_get_size_end;
   }

   for ( i = 0; i < dir_cache_count; i++) {
      if ( strcmp ( dev_name, dir_cache[i].dev_name) == 0) {
         this_index = i;
         break;
      }
   }

   if ( this_index != -1) {
      dev_t st_rdev = dir_cache[this_index].st_rdev -
                      ( dir_cache[this_index].st_rdev %
                        lvm_partition_count ( dir_cache[this_index].st_rdev));

      if ( lvm_check_partitioned_dev ( dir_cache[this_index].st_rdev) == TRUE) {
         for ( i = 0; i < dir_cache_count; i++) {
            if ( dir_cache[i].st_rdev == st_rdev)
               break;
         }
      }
   } else {
      ret = -LVM_EPV_GET_SIZE_LVM_DIR_CACHE;
      goto pv_get_size_end;
   }


   memset ( disk_dev_name, 0, sizeof ( disk_dev_name));
   strncpy ( disk_dev_name, dir_cache[i].dev_name,
             sizeof ( disk_dev_name) - 1);

   first = 1;
   if ( ( pv_handle = open ( disk_dev_name, O_RDONLY)) == -1) {
      ret = -LVM_EPV_GET_SIZE_OPEN;
   } else while ( ret == 0) {
#ifdef DEBUG
      debug ( "pv_get_size -- BEFORE llseek %X:%X\n",
              ( uint) ( offset >> 32),
              ( uint) ( offset & 0xFFFFFFFF));
#endif
      if ( llseek ( pv_handle, offset * SECTOR_SIZE, SEEK_SET) == -1) {
         ret = -LVM_EPV_GET_SIZE_LLSEEK;
         break;
      }

      memset ( buffer, 0, SECTOR_SIZE);
      if ( ( ret = read ( pv_handle, buffer, SECTOR_SIZE)) != SECTOR_SIZE) {
         ret = -LVM_EPV_GET_SIZE_READ;
         break;
      } else ret = 0;


      /* walk thrugh primary partitions */
      if ( s_buffer[255] == 0xAA55) {
         extended_flag = 0;
         for ( i = 0; i < 4; i++) {
#ifdef DEBUG
            debug ( "pv_get_size -- part[%d].sys_ind: %1X  "
                    "part[%d].nr_sects: %d\n",
                    i, part[i].sys_ind, i, part[i].nr_sects);
#endif
         /* is it a welcome extended partition? */
            if ( part[i].sys_ind == DOS_EXTENDED_PARTITION ||
                 part[i].sys_ind == LINUX_EXTENDED_PARTITION
#ifdef WIN98_EXTENDED_PARTITION
                 || part[i].sys_ind == WIN98_EXTENDED_PARTITION
#endif
            ) {
#ifdef DEBUG
#ifdef WIN98_EXTENDED_PARTITION
               debug ( "pv_get_size -- DOS/LINUX/WIN98_EXTENDED_PARTITION\n");
#else
               debug ( "pv_get_size -- DOS/LINUX_EXTENDED_PARTITION\n");
#endif
#endif
               extended_flag = 1;
               offset = extended_offset + part[i].start_sect;
               if ( extended_offset == 0) extended_offset = part[i].start_sect;
               if ( first == 1) part_i_tmp++;
            } else if ( first == 1) {
#ifdef DEBUG
               debug ( "pv_get_size -- first == 1\n");
#endif
               if ( i == part_i) {
                  if ( part[i].sys_ind == 0) ret = -LVM_EPV_GET_SIZE_NO_PRIMARY;
               } else part_i_tmp++;
            } else if ( part[i].sys_ind != 0) {
#ifdef DEBUG
               debug ( "pv_get_size -- first == 1\n");
#endif
               part_i_tmp++;
            }

            if ( part_i == part_i_tmp) {
#ifdef DEBUG
               debug ( "pv_get_size -- part_i == part_i_tmp\n");
#endif
               if ( part[i].nr_sects == 0) {
                  ioctl ( pv_handle, BLKGETSIZE, &ret);
                  i = 0;
                  memset ( &part[i], 0, sizeof ( struct partition));
               } else ret = part[i].nr_sects;
               goto pv_get_size_end;
            }
         }
      } else {
         ioctl ( pv_handle, BLKGETSIZE, &ret);
         i = 0;
         memset ( &part[i], 0, sizeof ( struct partition));
         goto pv_get_size_end;
      }

      first = 0;
      if ( extended_flag == 0 && part_i_tmp != part_i)
         ret = -LVM_EPV_GET_SIZE_NO_EXTENDED;
   }

pv_get_size_end:
   if ( part_ptr != NULL && ret > 0) memcpy ( part_ptr,
                                              &part[i],
                                              sizeof ( struct partition));

   if ( pv_handle != -1) close ( pv_handle);

#ifdef DEBUG
   debug ( "pv_get_size -- LEAVING with ret: %d\n", ret);
#endif
   return ret;
}
#else
int pv_get_size(char * dev_name, struct partition * dummy)
{
	int pv_fd, ret;

	pv_fd = open(dev_name, O_RDONLY);
	if (pv_fd < 0)
		return -LVM_EPV_GET_SIZE_OPEN;
	if (ioctl(pv_fd, BLKGETSIZE, &ret) < 0)
		return -LVM_EPV_GET_SIZE_IOCTL;
	if (close(pv_fd) < 0)
		perror("pv_get_size alpha - close"), exit(1);
	return ret;
}
#endif
