1/* bsd.cc -- Functions for loading and manipulating legacy BSD disklabel 2 data. */ 3 4/* By Rod Smith, initial coding August, 2009 */ 5 6/* This program is copyright (c) 2009 by Roderick W. Smith. It is distributed 7 under the terms of the GNU GPL version 2, as detailed in the COPYING file. */ 8 9#define __STDC_LIMIT_MACROS 10#ifndef __STDC_CONSTANT_MACROS 11#define __STDC_CONSTANT_MACROS 12#endif 13 14#include <stdio.h> 15//#include <unistd.h> 16#include <stdlib.h> 17#include <stdint.h> 18#include <fcntl.h> 19#include <sys/stat.h> 20#include <errno.h> 21#include <iostream> 22#include <string> 23#include "support.h" 24#include "bsd.h" 25 26using namespace std; 27 28 29BSDData::BSDData(void) { 30 state = unknown; 31 signature = UINT32_C(0); 32 signature2 = UINT32_C(0); 33 sectorSize = 512; 34 numParts = 0; 35 labelFirstLBA = 0; 36 labelLastLBA = 0; 37 labelStart = LABEL_OFFSET1; // assume raw disk format 38 partitions = NULL; 39} // default constructor 40 41BSDData::~BSDData(void) { 42 delete[] partitions; 43} // destructor 44 45// Read BSD disklabel data from the specified device filename. This function 46// just opens the device file and then calls an overloaded function to do 47// the bulk of the work. Returns 1 on success, 0 on failure. 48int BSDData::ReadBSDData(const string & device, uint64_t startSector, uint64_t endSector) { 49 int allOK = 1; 50 DiskIO myDisk; 51 52 if (device != "") { 53 if (myDisk.OpenForRead(device)) { 54 allOK = ReadBSDData(&myDisk, startSector, endSector); 55 } else { 56 allOK = 0; 57 } // if/else 58 59 myDisk.Close(); 60 } else { 61 allOK = 0; 62 } // if/else 63 return allOK; 64} // BSDData::ReadBSDData() (device filename version) 65 66// Load the BSD disklabel data from an already-opened disk 67// file, starting with the specified sector number. 68int BSDData::ReadBSDData(DiskIO *theDisk, uint64_t startSector, uint64_t endSector) { 69 int allOK = 1; 70 int i, foundSig = 0, bigEnd = 0; 71 int relative = 0; // assume absolute partition sector numbering 72 uint8_t buffer[4096]; // I/O buffer 73 uint32_t realSig; 74 uint32_t* temp32; 75 uint16_t* temp16; 76 BSDRecord* tempRecords; 77 int offset[NUM_OFFSETS] = { LABEL_OFFSET1, LABEL_OFFSET2 }; 78 79 labelFirstLBA = startSector; 80 labelLastLBA = endSector; 81 offset[1] = theDisk->GetBlockSize(); 82 83 // Read 4096 bytes (eight 512-byte sectors or equivalent) 84 // into memory; we'll extract data from this buffer. 85 // (Done to work around FreeBSD limitation on size of reads 86 // from block devices.) 87 allOK = theDisk->Seek(startSector); 88 if (allOK) allOK = theDisk->Read(buffer, 4096); 89 90 // Do some strangeness to support big-endian architectures... 91 bigEnd = (IsLittleEndian() == 0); 92 realSig = BSD_SIGNATURE; 93 if (bigEnd && allOK) 94 ReverseBytes(&realSig, 4); 95 96 // Look for the signature at any of two locations. 97 // Note that the signature is repeated at both the original 98 // offset and 132 bytes later, so we need two checks.... 99 if (allOK) { 100 i = 0; 101 do { 102 temp32 = (uint32_t*) &buffer[offset[i]]; 103 signature = *temp32; 104 if (signature == realSig) { // found first, look for second 105 temp32 = (uint32_t*) &buffer[offset[i] + 132]; 106 signature2 = *temp32; 107 if (signature2 == realSig) { 108 foundSig = 1; 109 labelStart = offset[i]; 110 } // if found signature 111 } // if/else 112 i++; 113 } while ((!foundSig) && (i < NUM_OFFSETS)); 114 allOK = foundSig; 115 } // if 116 117 // Load partition metadata from the buffer.... 118 if (allOK) { 119 temp32 = (uint32_t*) &buffer[labelStart + 40]; 120 sectorSize = *temp32; 121 temp16 = (uint16_t*) &buffer[labelStart + 138]; 122 numParts = *temp16; 123 } // if 124 125 // Make it big-endian-aware.... 126 if ((IsLittleEndian() == 0) && allOK) 127 ReverseMetaBytes(); 128 129 // Check validity of the data and flag it appropriately.... 130 if (foundSig && (numParts <= MAX_BSD_PARTS) && allOK) { 131 state = bsd; 132 } else { 133 state = bsd_invalid; 134 } // if/else 135 136 // If the state is good, go ahead and load the main partition data.... 137 if (state == bsd) { 138 partitions = new struct BSDRecord[numParts * sizeof(struct BSDRecord)]; 139 if (partitions == NULL) { 140 cerr << "Unable to allocate memory in BSDData::ReadBSDData()! Terminating!\n"; 141 exit(1); 142 } // if 143 for (i = 0; i < numParts; i++) { 144 // Once again, we use the buffer, but index it using a BSDRecord 145 // pointer (dangerous, but effective).... 146 tempRecords = (BSDRecord*) &buffer[labelStart + 148]; 147 partitions[i].lengthLBA = tempRecords[i].lengthLBA; 148 partitions[i].firstLBA = tempRecords[i].firstLBA; 149 partitions[i].fsType = tempRecords[i].fsType; 150 if (bigEnd) { // reverse data (fsType is a single byte) 151 ReverseBytes(&partitions[i].lengthLBA, 4); 152 ReverseBytes(&partitions[i].firstLBA, 4); 153 } // if big-endian 154 // Check for signs of relative sector numbering: A "0" first sector 155 // number on a partition with a non-zero length -- but ONLY if the 156 // length is less than the disk size, since NetBSD has a habit of 157 // creating a disk-sized partition within a carrier MBR partition 158 // that's too small to house it, and this throws off everything.... 159 if ((partitions[i].firstLBA == 0) && (partitions[i].lengthLBA > 0) 160 && (partitions[i].lengthLBA < labelLastLBA)) 161 relative = 1; 162 } // for 163 // Some disklabels use sector numbers relative to the enclosing partition's 164 // start, others use absolute sector numbers. If relative numbering was 165 // detected above, apply a correction to all partition start sectors.... 166 if (relative) { 167 for (i = 0; i < numParts; i++) { 168 partitions[i].firstLBA += (uint32_t) startSector; 169 } // for 170 } // if 171 } // if signatures OK 172// DisplayBSDData(); 173 return allOK; 174} // BSDData::ReadBSDData(DiskIO* theDisk, uint64_t startSector) 175 176// Reverse metadata's byte order; called only on big-endian systems 177void BSDData::ReverseMetaBytes(void) { 178 ReverseBytes(&signature, 4); 179 ReverseBytes(§orSize, 4); 180 ReverseBytes(&signature2, 4); 181 ReverseBytes(&numParts, 2); 182} // BSDData::ReverseMetaByteOrder() 183 184// Display basic BSD partition data. Used for debugging. 185void BSDData::DisplayBSDData(void) { 186 int i; 187 188 if (state == bsd) { 189 cout << "BSD partitions:\n"; 190 for (i = 0; i < numParts; i++) { 191 cout.width(4); 192 cout << i + 1 << "\t"; 193 cout.width(13); 194 cout << partitions[i].firstLBA << "\t"; 195 cout.width(15); 196 cout << partitions[i].lengthLBA << " \t0x"; 197 cout.width(2); 198 cout.fill('0'); 199 cout.setf(ios::uppercase); 200 cout << hex << (int) partitions[i].fsType << "\n" << dec; 201 cout.fill(' '); 202 } // for 203 } // if 204} // BSDData::DisplayBSDData() 205 206// Displays the BSD disklabel state. Called during program launch to inform 207// the user about the partition table(s) status 208int BSDData::ShowState(void) { 209 int retval = 0; 210 211 switch (state) { 212 case bsd_invalid: 213 cout << " BSD: not present\n"; 214 break; 215 case bsd: 216 cout << " BSD: present\n"; 217 retval = 1; 218 break; 219 default: 220 cout << "\a BSD: unknown -- bug!\n"; 221 break; 222 } // switch 223 return retval; 224} // BSDData::ShowState() 225 226// Weirdly, this function has stopped working when defined inline, 227// but it's OK here.... 228int BSDData::IsDisklabel(void) { 229 return (state == bsd); 230} // BSDData::IsDiskLabel() 231 232// Returns the BSD table's partition type code 233uint8_t BSDData::GetType(int i) { 234 uint8_t retval = 0; // 0 = "unused" 235 236 if ((i < numParts) && (i >= 0) && (state == bsd) && (partitions != 0)) 237 retval = partitions[i].fsType; 238 239 return(retval); 240} // BSDData::GetType() 241 242// Returns the number of the first sector of the specified partition 243uint64_t BSDData::GetFirstSector(int i) { 244 uint64_t retval = UINT64_C(0); 245 246 if ((i < numParts) && (i >= 0) && (state == bsd) && (partitions != 0)) 247 retval = (uint64_t) partitions[i].firstLBA; 248 249 return retval; 250} // BSDData::GetFirstSector 251 252// Returns the length (in sectors) of the specified partition 253uint64_t BSDData::GetLength(int i) { 254 uint64_t retval = UINT64_C(0); 255 256 if ((i < numParts) && (i >= 0) && (state == bsd) && (partitions != 0)) 257 retval = (uint64_t) partitions[i].lengthLBA; 258 259 return retval; 260} // BSDData::GetLength() 261 262// Returns the number of partitions defined in the current table 263int BSDData::GetNumParts(void) { 264 return numParts; 265} // BSDData::GetNumParts() 266 267// Returns the specified partition as a GPT partition. Used in BSD-to-GPT 268// conversion process 269GPTPart BSDData::AsGPT(int i) { 270 GPTPart guid; // dump data in here, then return it 271 uint64_t sectorOne, sectorEnd; // first & last sectors of partition 272 int passItOn = 1; // Set to 0 if partition is empty or invalid 273 274 guid.BlankPartition(); 275 sectorOne = (uint64_t) partitions[i].firstLBA; 276 sectorEnd = sectorOne + (uint64_t) partitions[i].lengthLBA; 277 if (sectorEnd > 0) sectorEnd--; 278 // Note on above: BSD partitions sometimes have a length of 0 and a start 279 // sector of 0. With unsigned ints, the usual way (start + length - 1) to 280 // find the end will result in a huge number, which will be confusing. 281 // Thus, apply the "-1" part only if it's reasonable to do so. 282 283 // Do a few sanity checks on the partition before we pass it on.... 284 // First, check that it falls within the bounds of its container 285 // and that it starts before it ends.... 286 if ((sectorOne < labelFirstLBA) || (sectorEnd > labelLastLBA) || (sectorOne > sectorEnd)) 287 passItOn = 0; 288 // Some disklabels include a pseudo-partition that's the size of the entire 289 // disk or containing partition. Don't return it. 290 if ((sectorOne <= labelFirstLBA) && (sectorEnd >= labelLastLBA) && 291 (GetType(i) == 0)) 292 passItOn = 0; 293 // If the end point is 0, it's not a valid partition. 294 if ((sectorEnd == 0) || (sectorEnd == labelFirstLBA)) 295 passItOn = 0; 296 297 if (passItOn) { 298 guid.SetFirstLBA(sectorOne); 299 guid.SetLastLBA(sectorEnd); 300 // Now set a random unique GUID for the partition.... 301 guid.RandomizeUniqueGUID(); 302 // ... zero out the attributes and name fields.... 303 guid.SetAttributes(UINT64_C(0)); 304 // Most BSD disklabel type codes seem to be archaic or rare. 305 // They're also ambiguous; a FreeBSD filesystem is impossible 306 // to distinguish from a NetBSD one. Thus, these code assignment 307 // are going to be rough to begin with. For a list of meanings, 308 // see http://fxr.watson.org/fxr/source/sys/dtype.h?v=DFBSD, 309 // or Google it. 310 switch (GetType(i)) { 311 case 1: // BSD swap 312 guid.SetType(0xa502); break; 313 case 7: // BSD FFS 314 guid.SetType(0xa503); break; 315 case 8: case 11: // MS-DOS or HPFS 316 guid.SetType(0x0700); break; 317 case 9: // log-structured fs 318 guid.SetType(0xa903); break; 319 case 13: // bootstrap 320 guid.SetType(0xa501); break; 321 case 14: // vinum 322 guid.SetType(0xa505); break; 323 case 15: // RAID 324 guid.SetType(0xa903); break; 325 case 27: // FreeBSD ZFS 326 guid.SetType(0xa504); break; 327 default: 328 guid.SetType(0xa503); break; 329 } // switch 330 // Set the partition name to the name of the type code.... 331 guid.SetName(guid.GetTypeName()); 332 } // if 333 return guid; 334} // BSDData::AsGPT() 335