bsd.cc 11.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/* bsd.cc -- Functions for loading and manipulating legacy BSD disklabel
   data. */

/* By Rod Smith, initial coding August, 2009 */

/* This program is copyright (c) 2009 by Roderick W. Smith. It is distributed
  under the terms of the GNU GPL version 2, as detailed in the COPYING file. */

#define __STDC_LIMIT_MACROS
#define __STDC_CONSTANT_MACROS

#include <stdio.h>
13
//#include <unistd.h>
14 15 16 17 18
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
19 20
#include <iostream>
#include <string>
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
#include "support.h"
#include "bsd.h"

using namespace std;


BSDData::BSDData(void) {
   state = unknown;
   signature = UINT32_C(0);
   signature2 = UINT32_C(0);
   sectorSize = 512;
   numParts = 0;
   labelFirstLBA = 0;
   labelLastLBA = 0;
   labelStart = LABEL_OFFSET1; // assume raw disk format
   partitions = NULL;
} // default constructor

BSDData::~BSDData(void) {
40
   delete[] partitions;
41 42 43 44
} // destructor

// Read BSD disklabel data from the specified device filename. This function
// just opens the device file and then calls an overloaded function to do
45 46 47 48 49 50 51 52
// the bulk of the work. Returns 1 on success, 0 on failure.
int BSDData::ReadBSDData(const string & device, uint64_t startSector, uint64_t endSector) {
   int allOK = 1;
   DiskIO myDisk;

   if (device != "") {
      if (myDisk.OpenForRead(device)) {
         allOK = ReadBSDData(&myDisk, startSector, endSector);
53 54 55 56
      } else {
         allOK = 0;
      } // if/else

57
      myDisk.Close();
58 59 60 61 62 63 64 65
   } else {
      allOK = 0;
   } // if/else
   return allOK;
} // BSDData::ReadBSDData() (device filename version)

// Load the BSD disklabel data from an already-opened disk
// file, starting with the specified sector number.
66 67 68
int BSDData::ReadBSDData(DiskIO *theDisk, uint64_t startSector, uint64_t endSector) {
   int allOK = 1;
   int i, foundSig = 0, bigEnd = 0;
69
   int relative = 0; // assume absolute partition sector numbering
70
   uint8_t buffer[4096]; // I/O buffer
71 72 73 74
   uint32_t realSig;
   uint32_t* temp32;
   uint16_t* temp16;
   BSDRecord* tempRecords;
75
   int offset[NUM_OFFSETS] = { LABEL_OFFSET1, LABEL_OFFSET2 };
76 77 78

   labelFirstLBA = startSector;
   labelLastLBA = endSector;
79
   offset[1] = theDisk->GetBlockSize();
80

81 82 83 84 85 86
   // Read 4096 bytes (eight 512-byte sectors or equivalent)
   // into memory; we'll extract data from this buffer.
   // (Done to work around FreeBSD limitation on size of reads
   // from block devices.)
   allOK = theDisk->Seek(startSector);
   if (allOK) allOK = theDisk->Read(buffer, 4096);
87 88 89 90

   // Do some strangeness to support big-endian architectures...
   bigEnd = (IsLittleEndian() == 0);
   realSig = BSD_SIGNATURE;
91
   if (bigEnd && allOK)
92 93
      ReverseBytes(&realSig, 4);

94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
   // Look for the signature at any of two locations.
   // Note that the signature is repeated at both the original
   // offset and 132 bytes later, so we need two checks....
   if (allOK) {
      i = 0;
      do {
         temp32 = (uint32_t*) &buffer[offset[i]];
         signature = *temp32;
         if (signature == realSig) { // found first, look for second
            temp32 = (uint32_t*) &buffer[offset[i] + 132];
            signature2 = *temp32;
            if (signature2 == realSig) {
               foundSig = 1;
               labelStart = offset[i];
            } // if found signature
         } // if/else
         i++;
      } while ((!foundSig) && (i < NUM_OFFSETS));
      allOK = foundSig;
113 114 115
   } // if

   // Load partition metadata from the buffer....
116 117 118 119 120 121
   if (allOK) {
      temp32 = (uint32_t*) &buffer[labelStart + 40];
      sectorSize = *temp32;
      temp16 = (uint16_t*) &buffer[labelStart + 138];
      numParts = *temp16;
   } // if
122 123

   // Make it big-endian-aware....
124
   if ((IsLittleEndian() == 0) && allOK)
125 126 127
      ReverseMetaBytes();

   // Check validity of the data and flag it appropriately....
128
   if (foundSig && (numParts <= MAX_BSD_PARTS) && allOK) {
129 130 131 132 133 134 135
      state = bsd;
   } else {
      state = bsd_invalid;
   } // if/else

   // If the state is good, go ahead and load the main partition data....
   if (state == bsd) {
136
      partitions = new struct BSDRecord[numParts * sizeof(struct BSDRecord)];
137 138 139 140
      if (partitions == NULL) {
         cerr << "Unable to allocate memory in BSDData::ReadBSDData()! Terminating!\n";
         exit(1);
      } // if
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
      for (i = 0; i < numParts; i++) {
         // Once again, we use the buffer, but index it using a BSDRecord
         // pointer (dangerous, but effective)....
         tempRecords = (BSDRecord*) &buffer[labelStart + 148];
         partitions[i].lengthLBA = tempRecords[i].lengthLBA;
         partitions[i].firstLBA = tempRecords[i].firstLBA;
         partitions[i].fsType = tempRecords[i].fsType;
         if (bigEnd) { // reverse data (fsType is a single byte)
            ReverseBytes(&partitions[i].lengthLBA, 4);
            ReverseBytes(&partitions[i].firstLBA, 4);
         } // if big-endian
         // Check for signs of relative sector numbering: A "0" first sector
         // number on a partition with a non-zero length -- but ONLY if the
         // length is less than the disk size, since NetBSD has a habit of
         // creating a disk-sized partition within a carrier MBR partition
         // that's too small to house it, and this throws off everything....
         if ((partitions[i].firstLBA == 0) && (partitions[i].lengthLBA > 0)
             && (partitions[i].lengthLBA < labelLastLBA))
            relative = 1;
      } // for
      // Some disklabels use sector numbers relative to the enclosing partition's
      // start, others use absolute sector numbers. If relative numbering was
      // detected above, apply a correction to all partition start sectors....
      if (relative) {
         for (i = 0; i < numParts; i++) {
166
            partitions[i].firstLBA += (uint32_t) startSector;
167 168 169 170
         } // for
      } // if
   } // if signatures OK
//   DisplayBSDData();
171 172
   return allOK;
} // BSDData::ReadBSDData(DiskIO* theDisk, uint64_t startSector)
173 174 175 176 177 178 179 180 181 182 183 184 185 186

// Reverse metadata's byte order; called only on big-endian systems
void BSDData::ReverseMetaBytes(void) {
   ReverseBytes(&signature, 4);
   ReverseBytes(&sectorSize, 4);
   ReverseBytes(&signature2, 4);
   ReverseBytes(&numParts, 2);
} // BSDData::ReverseMetaByteOrder()

// Display basic BSD partition data. Used for debugging.
void BSDData::DisplayBSDData(void) {
   int i;

   if (state == bsd) {
187
      cout << "BSD partitions:\n";
188
      for (i = 0; i < numParts; i++) {
189 190 191 192 193 194 195 196 197 198 199
         cout.width(4);
         cout << i + 1 << "\t";
         cout.width(13);
         cout << partitions[i].firstLBA << "\t";
         cout.width(15);
         cout << partitions[i].lengthLBA << " \t0x";
         cout.width(2);
         cout.fill('0');
         cout.setf(ios::uppercase);
         cout << hex << (int) partitions[i].fsType << "\n" << dec;
         cout.fill(' ');
200 201 202 203 204 205 206 207 208 209 210
      } // for
   } // if
} // BSDData::DisplayBSDData()

// Displays the BSD disklabel state. Called during program launch to inform
// the user about the partition table(s) status
int BSDData::ShowState(void) {
   int retval = 0;

   switch (state) {
      case bsd_invalid:
211
         cout << "  BSD: not present\n";
212 213
         break;
      case bsd:
214
         cout << "  BSD: present\n";
215 216 217
         retval = 1;
         break;
      default:
218
         cout << "\a  BSD: unknown -- bug!\n";
219 220 221 222 223
         break;
   } // switch
   return retval;
} // BSDData::ShowState()

224 225 226 227 228 229
// Weirdly, this function has stopped working when defined inline,
// but it's OK here....
int BSDData::IsDisklabel(void) {
   return (state == bsd);
} // BSDData::IsDiskLabel()

230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
// Returns the BSD table's partition type code
uint8_t BSDData::GetType(int i) {
   uint8_t retval = 0; // 0 = "unused"

   if ((i < numParts) && (i >= 0) && (state == bsd) && (partitions != 0))
      retval = partitions[i].fsType;

   return(retval);
} // BSDData::GetType()

// Returns the number of the first sector of the specified partition
uint64_t BSDData::GetFirstSector(int i) {
   uint64_t retval = UINT64_C(0);

   if ((i < numParts) && (i >= 0) && (state == bsd) && (partitions != 0))
      retval = (uint64_t) partitions[i].firstLBA;

   return retval;
} // BSDData::GetFirstSector

// Returns the length (in sectors) of the specified partition
uint64_t BSDData::GetLength(int i) {
   uint64_t retval = UINT64_C(0);

   if ((i < numParts) && (i >= 0) && (state == bsd) && (partitions != 0))
      retval = (uint64_t) partitions[i].lengthLBA;

   return retval;
} // BSDData::GetLength()

// Returns the number of partitions defined in the current table
int BSDData::GetNumParts(void) {
   return numParts;
} // BSDData::GetNumParts()

// Returns the specified partition as a GPT partition. Used in BSD-to-GPT
// conversion process
GPTPart BSDData::AsGPT(int i) {
   GPTPart guid;                  // dump data in here, then return it
   uint64_t sectorOne, sectorEnd; // first & last sectors of partition
   int passItOn = 1;              // Set to 0 if partition is empty or invalid

   guid.BlankPartition();
   sectorOne = (uint64_t) partitions[i].firstLBA;
   sectorEnd = sectorOne + (uint64_t) partitions[i].lengthLBA;
   if (sectorEnd > 0) sectorEnd--;
   // Note on above: BSD partitions sometimes have a length of 0 and a start
   // sector of 0. With unsigned ints, the usual way (start + length - 1) to
   // find the end will result in a huge number, which will be confusing.
   // Thus, apply the "-1" part only if it's reasonable to do so.

   // Do a few sanity checks on the partition before we pass it on....
   // First, check that it falls within the bounds of its container
   // and that it starts before it ends....
   if ((sectorOne < labelFirstLBA) || (sectorEnd > labelLastLBA) || (sectorOne > sectorEnd))
      passItOn = 0;
   // Some disklabels include a pseudo-partition that's the size of the entire
   // disk or containing partition. Don't return it.
   if ((sectorOne <= labelFirstLBA) && (sectorEnd >= labelLastLBA) &&
       (GetType(i) == 0))
      passItOn = 0;
   // If the end point is 0, it's not a valid partition.
   if ((sectorEnd == 0) || (sectorEnd == labelFirstLBA))
      passItOn = 0;

   if (passItOn) {
      guid.SetFirstLBA(sectorOne);
      guid.SetLastLBA(sectorEnd);
      // Now set a random unique GUID for the partition....
299
      guid.RandomizeUniqueGUID();
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
      // ... zero out the attributes and name fields....
      guid.SetAttributes(UINT64_C(0));
      // Most BSD disklabel type codes seem to be archaic or rare.
      // They're also ambiguous; a FreeBSD filesystem is impossible
      // to distinguish from a NetBSD one. Thus, these code assignment
      // are going to be rough to begin with. For a list of meanings,
      // see http://fxr.watson.org/fxr/source/sys/dtype.h?v=DFBSD,
      // or Google it.
      switch (GetType(i)) {
         case 1: // BSD swap
            guid.SetType(0xa502); break;
         case 7: // BSD FFS
            guid.SetType(0xa503); break;
         case 8: case 11: // MS-DOS or HPFS
            guid.SetType(0x0700); break;
         case 9: // log-structured fs
            guid.SetType(0xa903); break;
         case 13: // bootstrap
            guid.SetType(0xa501); break;
         case 14: // vinum
            guid.SetType(0xa505); break;
         case 15: // RAID
            guid.SetType(0xa903); break;
         case 27: // FreeBSD ZFS
            guid.SetType(0xa504); break;
         default:
326
            guid.SetType(0xa503); break;
327 328
      } // switch
      // Set the partition name to the name of the type code....
329
      guid.SetName(guid.GetTypeName());
330 331 332
   } // if
   return guid;
} // BSDData::AsGPT()