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