/*
 * tools/lib/pv_get_size.c
 *
 * Copyright (C) 1997 - 2001  Heinz Mauelshagen, Sistina Software
 *
 * March,October 1997
 * May 1998
 * January,March,June,July,September 1999
 * January,February 2000
 * June 2001
 *
 *
 * 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>
 *    03/02/2000 - use debug_enter()/debug_leave()
 *    16/02/2000 - added partiton table error return code
 *    15/03/2001 - Don't look for partitions on non-partition devices
 *    09/04/2001 - Move pv_check_partitioned_whole into this file from
 *                 program files
 *    26/06/2001 - checked in pv_get_size() if no hit in the dir cache
 *                 took place in order to avoid a segfault
 *
 */

#include <liblvm.h>

#ifndef WIN98_EXTENDED_PARTITION
#define WIN98_EXTENDED_PARTITION 0x0f
#endif

/* assumes pv_name has already been validated */
long long _pv_get_dev_size ( char *pv_name, struct partition *part_ptr)
{
   int pv_fd = -1;
   ulong size;
   long long ret = 1;

   debug_enter ( "_pv_get_dev_size -- CALLED with %s and %p\n",
                 pv_name, part_ptr);

   pv_fd = open ( pv_name, O_RDONLY);
   if ( pv_fd < 0)
      ret = -LVM_EPV_GET_SIZE_OPEN;
   else if ( ioctl ( pv_fd, BLKGETSIZE, &size) < 0)
      ret = -LVM_EPV_GET_SIZE_IOCTL;
   if ( pv_fd != -1) close ( pv_fd);

   if ( part_ptr)
      memset ( part_ptr, 0, sizeof ( struct partition));

   if ( ret > 0) ret = size;
   debug_leave ( "_pv_get_dev_size -- LEAVING with ret: %lu\n", ret);

   return ret;
}

#ifndef __alpha__
/* assumes pv_name has already been validated */

long pv_get_size ( char *pv_name, struct partition *part_ptr) {
   long long ret = pv_get_size_ll ( pv_name, part_ptr);
   if ( ret > UINT_MAX) ret = -1LL;
   return ( long) ret;
}

long long pv_get_size_ll ( char *pv_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;
   long long ret = 0;
   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];
   dev_t st_rdev = 0;
   dir_cache_t *dir_cache = NULL;
   dir_cache_t *cache_entry = NULL;

   debug_enter ( "pv_get_size -- CALLED with %s and %p\n",
                 pv_name, part_ptr);

   if ( pv_name == NULL) {
      ret = -LVM_EPARAM;
      goto pv_get_size_nopart;
   }

   /* If we don't have a partitioned device or we are looking at the
    * whole device rather than a partition, just return device size
    */
   if ( ( dir_cache_count = lvm_dir_cache ( &dir_cache)) < 1 ||
        ( cache_entry = lvm_dir_cache_find ( pv_name)) == NULL ||
        lvm_check_partitioned_dev ( cache_entry->st_rdev) == FALSE ||
        ( part_i = MINOR ( cache_entry->st_rdev) %
          lvm_partition_count ( cache_entry->st_rdev)) == 0) {
      ret = _pv_get_dev_size ( pv_name, part_ptr);
      goto pv_get_size_nopart;
   }

   /* Figure out first st_rdev in case of SCSI, IDE etc. */
   st_rdev = cache_entry->st_rdev - part_i;
   for ( i = 0; i < dir_cache_count; i++) {
      if ( dir_cache[i].st_rdev == st_rdev)
            break;
   }

   if ( i >= dir_cache_count) goto pv_get_size_nopart;
   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) {
      debug ( "pv_get_size -- BEFORE llseek %X:%X\n",
              ( uint) ( offset >> 32),
              ( uint) ( offset & 0xFFFFFFFF));
      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++) {
            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);
            /* is it a welcome extended partition? */
            if ( part[i].sys_ind == DOS_EXTENDED_PARTITION ||
                 part[i].sys_ind == LINUX_EXTENDED_PARTITION ||
                 part[i].sys_ind == WIN98_EXTENDED_PARTITION) {
               debug ( "pv_get_size -- DOS/LINUX/WIN98_EXTENDED_PARTITION\n");
               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) {
               debug ( "pv_get_size -- first == 1\n");
               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) {
               debug ( "pv_get_size -- first == 1\n");
               part_i_tmp++;
            }

            if ( part_i == part_i_tmp) {
               debug ( "pv_get_size -- part_i == part_i_tmp\n");
               if ( part[i].nr_sects == 0) {
                  ret = _pv_get_dev_size ( pv_name, part_ptr);
                  goto pv_get_size_nopart;
               } else ret = part[i].nr_sects;
               goto pv_get_size_end;
            }
         }
      } else {
         ret = _pv_get_dev_size ( pv_name, part_ptr);
         goto pv_get_size_nopart;
      }

      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));

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

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

#else /* #ifndef __alpha__ */

long long pv_get_size_ll ( char *pv_name, struct partition *dummy)
{
   return _pv_get_dev_size ( pv_name, dummy);
}

long pv_get_size ( char *pv_name, struct partition *dummy) {
   long long ret = _pv_get_dev_size(pv_name, dummy);
   if ( ret > UINT_MAX) ret = -1LL;
   return ( long) ret;
}
#endif
