/*
    MBRPart class, part of GPT fdisk program family.
    Copyright (C) 2011  Roderick W. Smith

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#define __STDC_LIMIT_MACROS
#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endif

#include <stddef.h>
#include <stdint.h>
#include <iostream>
#include "support.h"
#include "mbrpart.h"

using namespace std;

uint32_t MBRPart::numHeads = MAX_HEADS;
uint32_t MBRPart::numSecspTrack = MAX_SECSPERTRACK;
uint64_t MBRPart::diskSize = 0;
uint32_t MBRPart::blockSize = 512;
int MBRPart::numInstances = 0;

MBRPart::MBRPart() {
   int i;

   status = 0;
   for (i = 0; i < 3; i++) {
      firstSector[i] = 0;
      lastSector[i] = 0;
   } // for
   partitionType = 0x00;
   firstLBA = 0;
   lengthLBA = 0;
   includeAs = NONE;
   canBePrimary = 0;
   canBeLogical = 0;
   if (numInstances == 0) {
      numHeads = MAX_HEADS;
      numSecspTrack = MAX_SECSPERTRACK;
      diskSize = 0;
      blockSize = 512;
   } // if
   numInstances++;
}

MBRPart::MBRPart(const MBRPart& orig) {
   numInstances++;
   operator=(orig);
}

MBRPart::~MBRPart() {
   numInstances--;
}

MBRPart& MBRPart::operator=(const MBRPart& orig) {
   int i;

   status = orig.status;
   for (i = 0; i < 3; i++) {
      firstSector[i] = orig.firstSector[i];
      lastSector[i] = orig.lastSector[i];
   } // for
   partitionType = orig.partitionType;
   firstLBA = orig.firstLBA;
   lengthLBA = orig.lengthLBA;
   includeAs = orig.includeAs;
   canBePrimary = orig.canBePrimary;
   canBeLogical = orig.canBeLogical;
   return *this;
} // MBRPart::operator=(const MBRPart& orig)

// Set partition data from packed MBRRecord structure.
MBRPart& MBRPart::operator=(const struct MBRRecord& orig) {
   int i;

   status = orig.status;
   for (i = 0; i < 3; i++) {
      firstSector[i] = orig.firstSector[i];
      lastSector[i] = orig.lastSector[i];
   } // for
   partitionType = orig.partitionType;
   firstLBA = orig.firstLBA;
   lengthLBA = orig.lengthLBA;
   if (lengthLBA > 0)
      includeAs = PRIMARY;
   else
      includeAs = NONE;
   return *this;
} // MBRPart::operator=(const struct MBRRecord& orig)

// Compare the values, and return a bool result.
// Because this is intended for sorting and a lengthLBA value of 0 denotes
// a partition that's not in use and so that should be sorted upwards,
// we return the opposite of the usual arithmetic result when either
// lengthLBA value is 0.
bool MBRPart::operator<(const MBRPart &other) const {
   if (lengthLBA && other.lengthLBA)
      return (firstLBA < other.firstLBA);
   else
      return (other.firstLBA < firstLBA);
} // operator<()

/**************************************************
 *                                                *
 * Set information on partitions or disks without *
 * interacting with the user....                  *
 *                                                *
 **************************************************/

void MBRPart::SetGeometry(uint32_t heads, uint32_t sectors, uint64_t ds, uint32_t bs) {
   numHeads = heads;
   numSecspTrack = sectors;
   diskSize = ds;
   blockSize = bs;
} // MBRPart::SetGeometry

// Empty the partition (zero out all values).
void MBRPart::Empty(void) {
   status = UINT8_C(0);
   firstSector[0] = UINT8_C(0);
   firstSector[1] = UINT8_C(0);
   firstSector[2] = UINT8_C(0);
   partitionType = UINT8_C(0);
   lastSector[0] = UINT8_C(0);
   lastSector[1] = UINT8_C(0);
   lastSector[2] = UINT8_C(0);
   firstLBA = UINT32_C(0);
   lengthLBA = UINT32_C(0);
   includeAs = NONE;
} // MBRPart::Empty()

// Sets the type code, but silently refuses to change it to an extended type
// code.
// Returns 1 on success, 0 on failure (extended type code)
int MBRPart::SetType(uint8_t typeCode, int isExtended) {
   int allOK = 0;

   if ((isExtended == 1) || ((typeCode != 0x05) && (typeCode != 0x0f) && (typeCode != 0x85))) {
      partitionType = typeCode;
      allOK = 1;
   } // if
   return allOK;
} // MBRPart::SetType()

void MBRPart::SetStartLBA(uint64_t start) {
   if (start > UINT32_MAX)
      cerr << "Partition start out of range! Continuing, but problems now likely!\n";
   firstLBA = (uint32_t) start;
   RecomputeCHS();
} // MBRPart::SetStartLBA()

void MBRPart::SetLengthLBA(uint64_t length) {
   if (length > UINT32_MAX)
      cerr << "Partition length out of range! Continuing, but problems now likely!\n";
   lengthLBA = (uint32_t) length;
   RecomputeCHS();
} // MBRPart::SetLengthLBA()

// Set the start point and length of the partition. This function takes LBA
// values, sets them directly, and sets the CHS values based on the LBA
// values and the current geometry settings.
void MBRPart::SetLocation(uint64_t start, uint64_t length) {
   int validCHS;

   if ((start > UINT32_MAX) || (length > UINT32_MAX)) {
      cerr << "Partition values out of range in MBRPart::SetLocation()!\n"
           << "Continuing, but strange problems are now likely!\n";
   } // if
   firstLBA = (uint32_t) start;
   lengthLBA = (uint32_t) length;
   validCHS = RecomputeCHS();

   // If this is a complete 0xEE protective MBR partition, max out its
   // CHS last sector value, as per the GPT spec. (Set to 0xffffff,
   // although the maximum legal MBR value is 0xfeffff, which is
   // actually what GNU Parted and Apple's Disk Utility use, in
   // violation of the GPT spec.)
   if ((partitionType == 0xEE) && (!validCHS) && (firstLBA == 1) &&
       ((lengthLBA == diskSize - 1) || (lengthLBA == UINT32_MAX))) {
      lastSector[0] = lastSector[1] = lastSector[2] = 0xFF;
   } // if
} // MBRPart::SetLocation()

// Store the MBR data in the packed structure used for disk I/O...
void MBRPart::StoreInStruct(MBRRecord* theStruct) {
   int i;
   
   theStruct->firstLBA = firstLBA;
   theStruct->lengthLBA = lengthLBA;
   theStruct->partitionType = partitionType;
   theStruct->status = status;
   for (i = 0; i < 3; i++) {
      theStruct->firstSector[i] = firstSector[i];
      theStruct->lastSector[i] = lastSector[i];
   } // for
} // MBRPart::StoreInStruct()

/**********************************************
*                                            *
* Get information on partitions or disks.... *
*                                            *
**********************************************/

// Returns the last LBA value. Note that this can theoretically be a 33-bit
// value, so we return a 64-bit value. If lengthLBA == 0, returns 0, even if
// firstLBA is non-0.
uint64_t MBRPart::GetLastLBA(void) const {
   if (lengthLBA > 0)
      return (uint64_t) firstLBA + (uint64_t) lengthLBA - UINT64_C(1);
   else
      return 0;
} // MBRPart::GetLastLBA()

// Returns 1 if other overlaps with the current partition, 0 if they don't
// overlap
int MBRPart::DoTheyOverlap (const MBRPart& other) {
   return lengthLBA && other.lengthLBA &&
          (firstLBA <= other.GetLastLBA()) != (GetLastLBA() < other.firstLBA);
} // MBRPart::DoTheyOverlap()

/*************************************************
 *                                               *
 * Adjust information on partitions or disks.... *
 *                                               *
 *************************************************/

// Recompute the CHS values for the start and end points.
// Returns 1 if both computed values are within the range
// that can be expressed by that CHS, 0 otherwise.
int MBRPart::RecomputeCHS(void) {
   int retval = 1;

   if (lengthLBA > 0) {
      retval = LBAtoCHS(firstLBA, firstSector);
      retval *= LBAtoCHS(firstLBA + lengthLBA - 1, lastSector);
   } // if
   return retval;
} // MBRPart::RecomputeCHS()

// Converts 32-bit LBA value to MBR-style CHS value. Returns 1 if conversion
// was within the range that can be expressed by CHS (including 0, for an
// empty partition), 0 if the value is outside that range, and -1 if chs is
// invalid.
int MBRPart::LBAtoCHS(uint32_t lba, uint8_t * chs) {
   uint64_t cylinder, head, sector; // all numbered from 0
   uint64_t remainder;
   int retval = 1;
   int done = 0;

   if (chs != NULL) {
      // Special case: In case of 0 LBA value, zero out CHS values....
      if (lba == 0) {
         chs[0] = chs[1] = chs[2] = UINT8_C(0);
         done = 1;
      } // if
      // If LBA value is too large for CHS, max out CHS values....
      if ((!done) && (lba >= (numHeads * numSecspTrack * MAX_CYLINDERS))) {
         chs[0] = 254;
         chs[1] = chs[2] = 255;
         done = 1;
         retval = 0;
      } // if
      // If neither of the above applies, compute CHS values....
      if (!done) {
         cylinder = lba / (numHeads * numSecspTrack);
         remainder = lba - (cylinder * numHeads * numSecspTrack);
         head = remainder / numSecspTrack;
         remainder -= head * numSecspTrack;
         sector = remainder;
         if (head < numHeads)
            chs[0] = (uint8_t) head;
         else
            retval = 0;
         if (sector < numSecspTrack) {
            chs[1] = (uint8_t) ((sector + 1) + (cylinder >> 8) * 64);
            chs[2] = (uint8_t) (cylinder & UINT32_C(0xFF));
         } else {
            retval = 0;
         } // if/else
      } // if value is expressible and non-0
   } else { // Invalid (NULL) chs pointer
      retval = -1;
   } // if CHS pointer valid
   return (retval);
} // MBRPart::LBAtoCHS()

// Reverses the byte order, but only if we're on a big-endian platform.
// Note that most data come in 8-bit structures, so don't need reversing;
// only the LBA data needs to be reversed....
void MBRPart::ReverseByteOrder(void) {
   if (IsLittleEndian() == 0) {
      ReverseBytes(&firstLBA, 4);
      ReverseBytes(&lengthLBA, 4);
   } // if
} // MBRPart::ReverseByteOrder()

/**************************
 *                        *
 * User I/O functions.... *
 *                        *
 **************************/

// Show MBR data. Should update canBeLogical flags before calling.
// If isGpt == 1, omits the "can be logical" and "can be primary" columns.
void MBRPart::ShowData(int isGpt) {
   char bootCode = ' ';

   if (status & 0x80) // it's bootable
      bootCode = '*';
   cout.fill(' ');
   cout << bootCode << "  ";
   cout.width(13);
   cout << firstLBA;
   cout.width(13);
   cout << GetLastLBA() << "   ";
   switch (includeAs) {
      case PRIMARY:
         cout << "primary";
         break;
      case LOGICAL:
         cout << "logical";
         break;
      case NONE:
         cout << "omitted";
         break;
      default:
         cout << "error  ";
         break;
   } // switch
   cout.width(7);
   if (!isGpt) {
      if (canBeLogical)
         cout << "     Y      ";
      else
         cout << "            ";
      if (canBePrimary)
         cout << "  Y      ";
      else
         cout << "         ";
   } // if
   cout << "0x";
   cout.width(2);
   cout.fill('0');
   cout << hex << (int) partitionType << dec << "\n";
} // MBRPart::ShowData()
