1 /* 2 Copyright (C) 2010-2013 <Roderick W. Smith> 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation; either version 2 of the License, or 7 (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License along 15 with this program; if not, write to the Free Software Foundation, Inc., 16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 18 */ 19 20 /* This class implements an interactive text-mode interface atop the 21 GPTData class */ 22 23 #include <string.h> 24 #include <errno.h> 25 #include <stdint.h> 26 #include <limits.h> 27 #include <iostream> 28 #include <fstream> 29 #include <sstream> 30 #include <cstdio> 31 #include "attributes.h" 32 #include "gpttext.h" 33 #include "support.h" 34 35 using namespace std; 36 37 /******************************************** 38 * * 39 * GPTDataText class and related structures * 40 * * 41 ********************************************/ 42 43 GPTDataTextUI::GPTDataTextUI(void) : GPTData() { 44 } // default constructor 45 46 GPTDataTextUI::GPTDataTextUI(string filename) : GPTData(filename) { 47 } // constructor passing filename 48 49 GPTDataTextUI::~GPTDataTextUI(void) { 50 } // default destructor 51 52 /********************************************************************* 53 * * 54 * The following functions are extended (interactive) versions of * 55 * simpler functions in the base class.... * 56 * * 57 *********************************************************************/ 58 59 // Overridden function; calls base-class function and then makes 60 // additional queries of the user, if the base-class function can't 61 // decide what to do. 62 WhichToUse GPTDataTextUI::UseWhichPartitions(void) { 63 WhichToUse which; 64 MBRValidity mbrState; 65 int answer; 66 67 which = GPTData::UseWhichPartitions(); 68 if ((which != use_abort) || beQuiet) 69 return which; 70 71 // If we get past here, it means that the non-interactive tests were 72 // inconclusive, so we must ask the user which table to use.... 73 mbrState = protectiveMBR.GetValidity(); 74 75 if ((state == gpt_valid) && (mbrState == mbr)) { 76 cout << "Found valid MBR and GPT. Which do you want to use?\n"; 77 answer = GetNumber(1, 3, 2, " 1 - MBR\n 2 - GPT\n 3 - Create blank GPT\n\nYour answer: "); 78 if (answer == 1) { 79 which = use_mbr; 80 } else if (answer == 2) { 81 which = use_gpt; 82 cout << "Using GPT and creating fresh protective MBR.\n"; 83 } else which = use_new; 84 } // if 85 86 // Nasty decisions here -- GPT is present, but corrupt (bad CRCs or other 87 // problems) 88 if (state == gpt_corrupt) { 89 if ((mbrState == mbr) || (mbrState == hybrid)) { 90 cout << "Found valid MBR and corrupt GPT. Which do you want to use? (Using the\n" 91 << "GPT MAY permit recovery of GPT data.)\n"; 92 answer = GetNumber(1, 3, 2, " 1 - MBR\n 2 - GPT\n 3 - Create blank GPT\n\nYour answer: "); 93 if (answer == 1) { 94 which = use_mbr; 95 } else if (answer == 2) { 96 which = use_gpt; 97 } else which = use_new; 98 } else if (mbrState == invalid) { 99 cout << "Found invalid MBR and corrupt GPT. What do you want to do? (Using the\n" 100 << "GPT MAY permit recovery of GPT data.)\n"; 101 answer = GetNumber(1, 2, 1, " 1 - Use current GPT\n 2 - Create blank GPT\n\nYour answer: "); 102 if (answer == 1) { 103 which = use_gpt; 104 } else which = use_new; 105 } // if/else/else 106 } // if (corrupt GPT) 107 108 return which; 109 } // UseWhichPartitions() 110 111 // Ask the user for a partition number; and prompt for verification 112 // if the requested partition isn't of a known BSD type. 113 // Lets the base-class function do the work, and returns its value (the 114 // number of converted partitions). 115 int GPTDataTextUI::XFormDisklabel(void) { 116 uint32_t partNum; 117 uint16_t hexCode; 118 int goOn = 1, numDone = 0; 119 BSDData disklabel; 120 121 partNum = GetPartNum(); 122 123 // Now see if the specified partition has a BSD type code.... 124 hexCode = partitions[partNum].GetHexType(); 125 if ((hexCode != 0xa500) && (hexCode != 0xa900)) { 126 cout << "Specified partition doesn't have a disklabel partition type " 127 << "code.\nContinue anyway? "; 128 goOn = (GetYN() == 'Y'); 129 } // if 130 131 if (goOn) 132 numDone = GPTData::XFormDisklabel(partNum); 133 134 return numDone; 135 } // GPTData::XFormDisklabel(void) 136 137 138 /********************************************************************* 139 * * 140 * Begin functions that obtain information from the users, and often * 141 * do something with that information (call other functions) * 142 * * 143 *********************************************************************/ 144 145 // Prompts user for partition number and returns the result. Returns "0" 146 // (the first partition) if none are currently defined. 147 uint32_t GPTDataTextUI::GetPartNum(void) { 148 uint32_t partNum; 149 uint32_t low, high; 150 ostringstream prompt; 151 152 if (GetPartRange(&low, &high) > 0) { 153 prompt << "Partition number (" << low + 1 << "-" << high + 1 << "): "; 154 partNum = GetNumber(low + 1, high + 1, low, prompt.str()); 155 } else partNum = 1; 156 return (partNum - 1); 157 } // GPTDataTextUI::GetPartNum() 158 159 // What it says: Resize the partition table. (Default is 128 entries.) 160 void GPTDataTextUI::ResizePartitionTable(void) { 161 int newSize; 162 ostringstream prompt; 163 uint32_t curLow, curHigh; 164 165 cout << "Current partition table size is " << numParts << ".\n"; 166 GetPartRange(&curLow, &curHigh); 167 curHigh++; // since GetPartRange() returns numbers starting from 0... 168 // There's no point in having fewer than four partitions.... 169 if (curHigh < (blockSize / GPT_SIZE)) 170 curHigh = blockSize / GPT_SIZE; 171 prompt << "Enter new size (" << curHigh << " up, default " << NUM_GPT_ENTRIES << "): "; 172 newSize = GetNumber(4, 65535, 128, prompt.str()); 173 if (newSize < 128) { 174 cout << "Caution: The partition table size should officially be 16KB or larger,\n" 175 << "which works out to 128 entries. In practice, smaller tables seem to\n" 176 << "work with most OSes, but this practice is risky. I'm proceeding with\n" 177 << "the resize, but you may want to reconsider this action and undo it.\n\n"; 178 } // if 179 SetGPTSize(newSize); 180 } // GPTDataTextUI::ResizePartitionTable() 181 182 // Interactively create a partition 183 void GPTDataTextUI::CreatePartition(void) { 184 uint64_t firstBlock, firstInLargest, lastBlock, sector, origSector; 185 uint32_t firstFreePart = 0; 186 ostringstream prompt1, prompt2, prompt3; 187 int partNum; 188 189 // Find first free partition... 190 while (partitions[firstFreePart].GetFirstLBA() != 0) { 191 firstFreePart++; 192 } // while 193 194 if (((firstBlock = FindFirstAvailable()) != 0) && 195 (firstFreePart < numParts)) { 196 lastBlock = FindLastAvailable(); 197 firstInLargest = FindFirstInLargest(); 198 Align(&firstInLargest); 199 200 // Get partition number.... 201 prompt1 << "Partition number (" << firstFreePart + 1 << "-" << numParts 202 << ", default " << firstFreePart + 1 << "): "; 203 do { 204 partNum = GetNumber(firstFreePart + 1, numParts, 205 firstFreePart + 1, prompt1.str()) - 1; 206 if (partitions[partNum].GetFirstLBA() != 0) 207 cout << "partition " << partNum + 1 << " is in use.\n"; 208 } while (partitions[partNum].GetFirstLBA() != 0); 209 210 // Get first block for new partition... 211 prompt2 << "First sector (" << firstBlock << "-" << lastBlock << ", default = " 212 << firstInLargest << ") or {+-}size{KMGTP}: "; 213 do { 214 sector = GetSectorNum(firstBlock, lastBlock, firstInLargest, blockSize, prompt2.str()); 215 } while (IsFree(sector) == 0); 216 origSector = sector; 217 if (Align(§or)) { 218 cout << "Information: Moved requested sector from " << origSector << " to " 219 << sector << " in\norder to align on " << sectorAlignment 220 << "-sector boundaries.\n"; 221 if (!beQuiet) 222 cout << "Use 'l' on the experts' menu to adjust alignment\n"; 223 } // if 224 // Align(§or); // Align sector to correct multiple 225 firstBlock = sector; 226 227 // Get last block for new partitions... 228 lastBlock = FindLastInFree(firstBlock); 229 prompt3 << "Last sector (" << firstBlock << "-" << lastBlock << ", default = " 230 << lastBlock << ") or {+-}size{KMGTP}: "; 231 do { 232 sector = GetSectorNum(firstBlock, lastBlock, lastBlock, blockSize, prompt3.str()); 233 } while (IsFree(sector) == 0); 234 lastBlock = sector; 235 236 firstFreePart = GPTData::CreatePartition(partNum, firstBlock, lastBlock); 237 partitions[partNum].ChangeType(); 238 partitions[partNum].SetDefaultDescription(); 239 } else { 240 if (firstFreePart >= numParts) 241 cout << "No table partition entries left\n"; 242 else 243 cout << "No free sectors available\n"; 244 } // if/else 245 } // GPTDataTextUI::CreatePartition() 246 247 // Interactively delete a partition (duh!) 248 void GPTDataTextUI::DeletePartition(void) { 249 int partNum; 250 uint32_t low, high; 251 ostringstream prompt; 252 253 if (GetPartRange(&low, &high) > 0) { 254 prompt << "Partition number (" << low + 1 << "-" << high + 1 << "): "; 255 partNum = GetNumber(low + 1, high + 1, low, prompt.str()); 256 GPTData::DeletePartition(partNum - 1); 257 } else { 258 cout << "No partitions\n"; 259 } // if/else 260 } // GPTDataTextUI::DeletePartition() 261 262 // Prompt user for a partition number, then change its type code 263 void GPTDataTextUI::ChangePartType(void) { 264 int partNum; 265 uint32_t low, high; 266 267 if (GetPartRange(&low, &high) > 0) { 268 partNum = GetPartNum(); 269 partitions[partNum].ChangeType(); 270 } else { 271 cout << "No partitions\n"; 272 } // if/else 273 } // GPTDataTextUI::ChangePartType() 274 275 // Prompt user for a partition number, then change its unique 276 // GUID. 277 void GPTDataTextUI::ChangeUniqueGuid(void) { 278 int partNum; 279 uint32_t low, high; 280 string guidStr; 281 282 if (GetPartRange(&low, &high) > 0) { 283 partNum = GetPartNum(); 284 cout << "Enter the partition's new unique GUID ('R' to randomize): "; 285 guidStr = ReadString(); 286 if ((guidStr.length() >= 32) || (guidStr[0] == 'R') || (guidStr[0] == 'r')) { 287 SetPartitionGUID(partNum, (GUIDData) guidStr); 288 cout << "New GUID is " << partitions[partNum].GetUniqueGUID() << "\n"; 289 } else { 290 cout << "GUID is too short!\n"; 291 } // if/else 292 } else 293 cout << "No partitions\n"; 294 } // GPTDataTextUI::ChangeUniqueGuid() 295 296 // Partition attributes seem to be rarely used, but I want a way to 297 // adjust them for completeness.... 298 void GPTDataTextUI::SetAttributes(uint32_t partNum) { 299 partitions[partNum].SetAttributes(); 300 } // GPTDataTextUI::SetAttributes() 301 302 // Prompts the user for a partition name and sets the partition's 303 // name. Returns 1 on success, 0 on failure (invalid partition 304 // number). (Note that the function skips prompting when an 305 // invalid partition number is detected.) 306 int GPTDataTextUI::SetName(uint32_t partNum) { 307 UnicodeString theName = ""; 308 int retval = 1; 309 310 if (IsUsedPartNum(partNum)) { 311 cout << "Enter name: "; 312 #ifdef USE_UTF16 313 theName = ReadUString(); 314 #else 315 theName = ReadString(); 316 #endif 317 partitions[partNum].SetName(theName); 318 } else { 319 cerr << "Invalid partition number (" << partNum << ")\n"; 320 retval = 0; 321 } // if/else 322 323 return retval; 324 } // GPTDataTextUI::SetName() 325 326 // Ask user for two partition numbers and swap them in the table. Note that 327 // this just reorders table entries; it doesn't adjust partition layout on 328 // the disk. 329 // Returns 1 if successful, 0 if not. (If user enters identical numbers, it 330 // counts as successful.) 331 int GPTDataTextUI::SwapPartitions(void) { 332 int partNum1, partNum2, didIt = 0; 333 uint32_t low, high; 334 ostringstream prompt; 335 GPTPart temp; 336 337 if (GetPartRange(&low, &high) > 0) { 338 partNum1 = GetPartNum(); 339 if (high >= numParts - 1) 340 high = 0; 341 prompt << "New partition number (1-" << numParts 342 << ", default " << high + 2 << "): "; 343 partNum2 = GetNumber(1, numParts, high + 2, prompt.str()) - 1; 344 didIt = GPTData::SwapPartitions(partNum1, partNum2); 345 } else { 346 cout << "No partitions\n"; 347 } // if/else 348 return didIt; 349 } // GPTDataTextUI::SwapPartitionNumbers() 350 351 // This function destroys the on-disk GPT structures. Returns 1 if the user 352 // confirms destruction, 0 if the user aborts or if there's a disk error. 353 int GPTDataTextUI::DestroyGPTwPrompt(void) { 354 int allOK = 1; 355 356 if ((apmFound) || (bsdFound)) { 357 cout << "WARNING: APM or BSD disklabel structures detected! This operation could\n" 358 << "damage any APM or BSD partitions on this disk!\n"; 359 } // if APM or BSD 360 cout << "\a\aAbout to wipe out GPT on " << device << ". Proceed? "; 361 if (GetYN() == 'Y') { 362 if (DestroyGPT()) { 363 // Note on below: Touch the MBR only if the user wants it completely 364 // blanked out. Version 0.4.2 deleted the 0xEE partition and re-wrote 365 // the MBR, but this could wipe out a valid MBR that the program 366 // had subsequently discarded (say, if it conflicted with older GPT 367 // structures). 368 cout << "Blank out MBR? "; 369 if (GetYN() == 'Y') { 370 DestroyMBR(); 371 } else { 372 cout << "MBR is unchanged. You may need to delete an EFI GPT (0xEE) partition\n" 373 << "with fdisk or another tool.\n"; 374 } // if/else 375 } else allOK = 0; // if GPT structures destroyed 376 } else allOK = 0; // if user confirms destruction 377 return (allOK); 378 } // GPTDataTextUI::DestroyGPTwPrompt() 379 380 // Get partition number from user and then call ShowPartDetails(partNum) 381 // to show its detailed information 382 void GPTDataTextUI::ShowDetails(void) { 383 int partNum; 384 uint32_t low, high; 385 386 if (GetPartRange(&low, &high) > 0) { 387 partNum = GetPartNum(); 388 ShowPartDetails(partNum); 389 } else { 390 cout << "No partitions\n"; 391 } // if/else 392 } // GPTDataTextUI::ShowDetails() 393 394 // Create a hybrid MBR -- an ugly, funky thing that helps GPT work with 395 // OSes that don't understand GPT. 396 void GPTDataTextUI::MakeHybrid(void) { 397 uint32_t partNums[3] = {0, 0, 0}; 398 string line; 399 int numPartsToCvt = 0, numConverted = 0, i, j, mbrNum = 0; 400 unsigned int hexCode = 0; 401 MBRPart hybridPart; 402 MBRData hybridMBR; 403 char eeFirst = 'Y'; // Whether EFI GPT (0xEE) partition comes first in table 404 405 cout << "\nWARNING! Hybrid MBRs are flaky and dangerous! If you decide not to use one,\n" 406 << "just hit the Enter key at the below prompt and your MBR partition table will\n" 407 << "be untouched.\n\n\a"; 408 409 // Use a local MBR structure, copying from protectiveMBR to keep its 410 // boot loader code intact.... 411 hybridMBR = protectiveMBR; 412 hybridMBR.EmptyMBR(0); 413 414 // Now get the numbers of up to three partitions to add to the 415 // hybrid MBR.... 416 cout << "Type from one to three GPT partition numbers, separated by spaces, to be\n" 417 << "added to the hybrid MBR, in sequence: "; 418 line = ReadString(); 419 istringstream inLine(line); 420 do { 421 inLine >> partNums[numPartsToCvt]; 422 if (partNums[numPartsToCvt] > 0) 423 numPartsToCvt++; 424 } while (!inLine.eof() && (numPartsToCvt < 3)); 425 426 if (numPartsToCvt > 0) { 427 cout << "Place EFI GPT (0xEE) partition first in MBR (good for GRUB)? "; 428 eeFirst = GetYN(); 429 } // if 430 431 for (i = 0; i < numPartsToCvt; i++) { 432 j = partNums[i] - 1; 433 if (partitions[j].IsUsed() && (partitions[j].IsSizedForMBR() != MBR_SIZED_BAD)) { 434 mbrNum = i + (eeFirst == 'Y'); 435 cout << "\nCreating entry for GPT partition #" << j + 1 436 << " (MBR partition #" << mbrNum + 1 << ")\n"; 437 hybridPart.SetType(GetMBRTypeCode(partitions[j].GetHexType() / 256)); 438 hybridPart.SetLocation(partitions[j].GetFirstLBA(), partitions[j].GetLengthLBA()); 439 hybridPart.SetInclusion(PRIMARY); 440 cout << "Set the bootable flag? "; 441 if (GetYN() == 'Y') 442 hybridPart.SetStatus(0x80); 443 else 444 hybridPart.SetStatus(0x00); 445 hybridPart.SetInclusion(PRIMARY); 446 if (partitions[j].IsSizedForMBR() == MBR_SIZED_IFFY) 447 WarnAboutIffyMBRPart(j + 1); 448 numConverted++; 449 } else { 450 cerr << "\nGPT partition #" << j + 1 << " does not exist or is too big; skipping.\n"; 451 } // if/else 452 hybridMBR.AddPart(mbrNum, hybridPart); 453 } // for 454 455 if (numConverted > 0) { // User opted to create a hybrid MBR.... 456 // Create EFI protective partition that covers the start of the disk. 457 // If this location (covering the main GPT data structures) is omitted, 458 // Linux won't find any partitions on the disk. 459 hybridPart.SetLocation(1, hybridMBR.FindLastInFree(1)); 460 hybridPart.SetStatus(0); 461 hybridPart.SetType(0xEE); 462 hybridPart.SetInclusion(PRIMARY); 463 // newNote firstLBA and lastLBA are computed later... 464 if (eeFirst == 'Y') { 465 hybridMBR.AddPart(0, hybridPart); 466 } else { 467 hybridMBR.AddPart(numConverted, hybridPart); 468 } // else 469 hybridMBR.SetHybrid(); 470 471 // ... and for good measure, if there are any partition spaces left, 472 // optionally create another protective EFI partition to cover as much 473 // space as possible.... 474 if (hybridMBR.CountParts() < 4) { // unused entry.... 475 cout << "\nUnused partition space(s) found. Use one to protect more partitions? "; 476 if (GetYN() == 'Y') { 477 cout << "Note: Default is 0xEE, but this may confuse Mac OS X.\n"; 478 // Comment on above: Mac OS treats disks with more than one 479 // 0xEE MBR partition as MBR disks, not as GPT disks. 480 hexCode = GetMBRTypeCode(0xEE); 481 hybridMBR.MakeBiggestPart(3, hexCode); 482 } // if (GetYN() == 'Y') 483 } // if unused entry 484 protectiveMBR = hybridMBR; 485 } else { 486 cout << "\nNo partitions converted; original protective/hybrid MBR is unmodified!\n"; 487 } // if/else (numConverted > 0) 488 } // GPTDataTextUI::MakeHybrid() 489 490 // Convert the GPT to MBR form, storing partitions in the protectiveMBR 491 // variable. This function is necessarily limited; it may not be able to 492 // convert all partitions, depending on the disk size and available space 493 // before each partition (one free sector is required to create a logical 494 // partition, which are necessary to convert more than four partitions). 495 // Returns the number of converted partitions; if this value 496 // is over 0, the calling function should call DestroyGPT() to destroy 497 // the GPT data, call SaveMBR() to save the MBR, and then exit. 498 int GPTDataTextUI::XFormToMBR(void) { 499 uint32_t i; 500 501 protectiveMBR.EmptyMBR(0); 502 for (i = 0; i < numParts; i++) { 503 if (partitions[i].IsUsed()) { 504 if (partitions[i].IsSizedForMBR() == MBR_SIZED_IFFY) 505 WarnAboutIffyMBRPart(i + 1); 506 // Note: MakePart() checks for oversized partitions, so don't 507 // bother checking other IsSizedForMBR() return values.... 508 protectiveMBR.MakePart(i, partitions[i].GetFirstLBA(), 509 partitions[i].GetLengthLBA(), 510 partitions[i].GetHexType() / 0x0100, 0); 511 } // if 512 } // for 513 protectiveMBR.MakeItLegal(); 514 return protectiveMBR.DoMenu(); 515 } // GPTDataTextUI::XFormToMBR() 516 517 /****************************************************** 518 * * 519 * Display informational messages for the user.... * 520 * * 521 ******************************************************/ 522 523 // Although an MBR partition that begins below sector 2^32 and is less than 2^32 sectors in 524 // length is technically legal even if it ends above the 2^32-sector mark, such a partition 525 // tends to confuse a lot of OSes, so warn the user about such partitions. This function is 526 // called by XFormToMBR() and MakeHybrid(); it's a separate function just to consolidate the 527 // lengthy message in one place. 528 void GPTDataTextUI::WarnAboutIffyMBRPart(int partNum) { 529 cout << "\a\nWarning! GPT partition " << partNum << " ends after the 2^32 sector mark! The partition\n" 530 << "begins before this point, and is smaller than 2^32 sectors. This is technically\n" 531 << "legal, but will confuse some OSes. The partition IS being added to the MBR, but\n" 532 << "if your OS misbehaves or can't see the partition, the partition may simply be\n" 533 << "unusable in that OS and may need to be resized or omitted from the MBR.\n\n"; 534 } // GPTDataTextUI::WarnAboutIffyMBRPart() 535 536 /********************************************************************* 537 * * 538 * The following functions provide the main menus for the gdisk * 539 * program.... * 540 * * 541 *********************************************************************/ 542 543 // Accept a command and execute it. Returns only when the user 544 // wants to exit (such as after a 'w' or 'q' command). 545 void GPTDataTextUI::MainMenu(string filename) { 546 int goOn = 1; 547 PartType typeHelper; 548 uint32_t temp1, temp2; 549 550 do { 551 cout << "\nCommand (? for help): "; 552 switch (ReadString()[0]) { 553 case '\0': 554 goOn = cin.good(); 555 break; 556 case 'b': case 'B': 557 cout << "Enter backup filename to save: "; 558 SaveGPTBackup(ReadString()); 559 break; 560 case 'c': case 'C': 561 if (GetPartRange(&temp1, &temp2) > 0) 562 SetName(GetPartNum()); 563 else 564 cout << "No partitions\n"; 565 break; 566 case 'd': case 'D': 567 DeletePartition(); 568 break; 569 case 'i': case 'I': 570 ShowDetails(); 571 break; 572 case 'l': case 'L': 573 typeHelper.ShowAllTypes(); 574 break; 575 case 'n': case 'N': 576 CreatePartition(); 577 break; 578 case 'o': case 'O': 579 cout << "This option deletes all partitions and creates a new protective MBR.\n" 580 << "Proceed? "; 581 if (GetYN() == 'Y') { 582 ClearGPTData(); 583 MakeProtectiveMBR(); 584 } // if 585 break; 586 case 'p': case 'P': 587 DisplayGPTData(); 588 break; 589 case 'q': case 'Q': 590 goOn = 0; 591 break; 592 case 'r': case 'R': 593 RecoveryMenu(filename); 594 goOn = 0; 595 break; 596 case 's': case 'S': 597 SortGPT(); 598 cout << "You may need to edit /etc/fstab and/or your boot loader configuration!\n"; 599 break; 600 case 't': case 'T': 601 ChangePartType(); 602 break; 603 case 'v': case 'V': 604 Verify(); 605 break; 606 case 'w': case 'W': 607 if (SaveGPTData() == 1) 608 goOn = 0; 609 break; 610 case 'x': case 'X': 611 ExpertsMenu(filename); 612 goOn = 0; 613 break; 614 default: 615 ShowCommands(); 616 break; 617 } // switch 618 } while (goOn); 619 } // GPTDataTextUI::MainMenu() 620 621 void GPTDataTextUI::ShowCommands(void) { 622 cout << "b\tback up GPT data to a file\n"; 623 cout << "c\tchange a partition's name\n"; 624 cout << "d\tdelete a partition\n"; 625 cout << "i\tshow detailed information on a partition\n"; 626 cout << "l\tlist known partition types\n"; 627 cout << "n\tadd a new partition\n"; 628 cout << "o\tcreate a new empty GUID partition table (GPT)\n"; 629 cout << "p\tprint the partition table\n"; 630 cout << "q\tquit without saving changes\n"; 631 cout << "r\trecovery and transformation options (experts only)\n"; 632 cout << "s\tsort partitions\n"; 633 cout << "t\tchange a partition's type code\n"; 634 cout << "v\tverify disk\n"; 635 cout << "w\twrite table to disk and exit\n"; 636 cout << "x\textra functionality (experts only)\n"; 637 cout << "?\tprint this menu\n"; 638 } // GPTDataTextUI::ShowCommands() 639 640 // Accept a recovery & transformation menu command. Returns only when the user 641 // issues an exit command, such as 'w' or 'q'. 642 void GPTDataTextUI::RecoveryMenu(string filename) { 643 uint32_t numParts; 644 int goOn = 1, temp1; 645 646 do { 647 cout << "\nRecovery/transformation command (? for help): "; 648 switch (ReadString()[0]) { 649 case '\0': 650 goOn = cin.good(); 651 break; 652 case 'b': case 'B': 653 RebuildMainHeader(); 654 break; 655 case 'c': case 'C': 656 cout << "Warning! This will probably do weird things if you've converted an MBR to\n" 657 << "GPT form and haven't yet saved the GPT! Proceed? "; 658 if (GetYN() == 'Y') 659 LoadSecondTableAsMain(); 660 break; 661 case 'd': case 'D': 662 RebuildSecondHeader(); 663 break; 664 case 'e': case 'E': 665 cout << "Warning! This will probably do weird things if you've converted an MBR to\n" 666 << "GPT form and haven't yet saved the GPT! Proceed? "; 667 if (GetYN() == 'Y') 668 LoadMainTable(); 669 break; 670 case 'f': case 'F': 671 cout << "Warning! This will destroy the currently defined partitions! Proceed? "; 672 if (GetYN() == 'Y') { 673 if (LoadMBR(filename) == 1) { // successful load 674 XFormPartitions(); 675 } else { 676 cout << "Problem loading MBR! GPT is untouched; regenerating protective MBR!\n"; 677 MakeProtectiveMBR(); 678 } // if/else 679 } // if 680 break; 681 case 'g': case 'G': 682 numParts = GetNumParts(); 683 temp1 = XFormToMBR(); 684 if (temp1 > 0) 685 cout << "\nConverted " << temp1 << " partitions. Finalize and exit? "; 686 if ((temp1 > 0) && (GetYN() == 'Y')) { 687 if ((DestroyGPT() > 0) && (SaveMBR())) { 688 goOn = 0; 689 } // if 690 } else { 691 MakeProtectiveMBR(); 692 SetGPTSize(numParts, 0); 693 cout << "Note: New protective MBR created\n\n"; 694 } // if/else 695 break; 696 case 'h': case 'H': 697 MakeHybrid(); 698 break; 699 case 'i': case 'I': 700 ShowDetails(); 701 break; 702 case 'l': case 'L': 703 cout << "Enter backup filename to load: "; 704 LoadGPTBackup(ReadString()); 705 break; 706 case 'm': case 'M': 707 MainMenu(filename); 708 goOn = 0; 709 break; 710 case 'o': case 'O': 711 DisplayMBRData(); 712 break; 713 case 'p': case 'P': 714 DisplayGPTData(); 715 break; 716 case 'q': case 'Q': 717 goOn = 0; 718 break; 719 case 't': case 'T': 720 XFormDisklabel(); 721 break; 722 case 'v': case 'V': 723 Verify(); 724 break; 725 case 'w': case 'W': 726 if (SaveGPTData() == 1) { 727 goOn = 0; 728 } // if 729 break; 730 case 'x': case 'X': 731 ExpertsMenu(filename); 732 goOn = 0; 733 break; 734 default: 735 ShowRecoveryCommands(); 736 break; 737 } // switch 738 } while (goOn); 739 } // GPTDataTextUI::RecoveryMenu() 740 741 void GPTDataTextUI::ShowRecoveryCommands(void) { 742 cout << "b\tuse backup GPT header (rebuilding main)\n"; 743 cout << "c\tload backup partition table from disk (rebuilding main)\n"; 744 cout << "d\tuse main GPT header (rebuilding backup)\n"; 745 cout << "e\tload main partition table from disk (rebuilding backup)\n"; 746 cout << "f\tload MBR and build fresh GPT from it\n"; 747 cout << "g\tconvert GPT into MBR and exit\n"; 748 cout << "h\tmake hybrid MBR\n"; 749 cout << "i\tshow detailed information on a partition\n"; 750 cout << "l\tload partition data from a backup file\n"; 751 cout << "m\treturn to main menu\n"; 752 cout << "o\tprint protective MBR data\n"; 753 cout << "p\tprint the partition table\n"; 754 cout << "q\tquit without saving changes\n"; 755 cout << "t\ttransform BSD disklabel partition\n"; 756 cout << "v\tverify disk\n"; 757 cout << "w\twrite table to disk and exit\n"; 758 cout << "x\textra functionality (experts only)\n"; 759 cout << "?\tprint this menu\n"; 760 } // GPTDataTextUI::ShowRecoveryCommands() 761 762 // Accept an experts' menu command. Returns only after the user 763 // selects an exit command, such as 'w' or 'q'. 764 void GPTDataTextUI::ExpertsMenu(string filename) { 765 GPTData secondDevice; 766 uint32_t temp1, temp2; 767 int goOn = 1; 768 string guidStr, device; 769 GUIDData aGUID; 770 ostringstream prompt; 771 772 do { 773 cout << "\nExpert command (? for help): "; 774 switch (ReadString()[0]) { 775 case '\0': 776 goOn = cin.good(); 777 break; 778 case 'a': case 'A': 779 if (GetPartRange(&temp1, &temp2) > 0) 780 SetAttributes(GetPartNum()); 781 else 782 cout << "No partitions\n"; 783 break; 784 case 'c': case 'C': 785 ChangeUniqueGuid(); 786 break; 787 case 'd': case 'D': 788 cout << "Partitions will begin on " << GetAlignment() 789 << "-sector boundaries.\n"; 790 break; 791 case 'e': case 'E': 792 cout << "Relocating backup data structures to the end of the disk\n"; 793 MoveSecondHeaderToEnd(); 794 break; 795 case 'f': case 'F': 796 RandomizeGUIDs(); 797 break; 798 case 'g': case 'G': 799 cout << "Enter the disk's unique GUID ('R' to randomize): "; 800 guidStr = ReadString(); 801 if ((guidStr.length() >= 32) || (guidStr[0] == 'R') || (guidStr[0] == 'r')) { 802 SetDiskGUID((GUIDData) guidStr); 803 cout << "The new disk GUID is " << GetDiskGUID() << "\n"; 804 } else { 805 cout << "GUID is too short!\n"; 806 } // if/else 807 break; 808 case 'h': case 'H': 809 RecomputeCHS(); 810 break; 811 case 'i': case 'I': 812 ShowDetails(); 813 break; 814 case 'l': case 'L': 815 prompt.seekp(0); 816 prompt << "Enter the sector alignment value (1-" << MAX_ALIGNMENT << ", default = " 817 << DEFAULT_ALIGNMENT << "): "; 818 temp1 = GetNumber(1, MAX_ALIGNMENT, DEFAULT_ALIGNMENT, prompt.str()); 819 SetAlignment(temp1); 820 break; 821 case 'm': case 'M': 822 MainMenu(filename); 823 goOn = 0; 824 break; 825 case 'n': case 'N': 826 MakeProtectiveMBR(); 827 break; 828 case 'o': case 'O': 829 DisplayMBRData(); 830 break; 831 case 'p': case 'P': 832 DisplayGPTData(); 833 break; 834 case 'q': case 'Q': 835 goOn = 0; 836 break; 837 case 'r': case 'R': 838 RecoveryMenu(filename); 839 goOn = 0; 840 break; 841 case 's': case 'S': 842 ResizePartitionTable(); 843 break; 844 case 't': case 'T': 845 SwapPartitions(); 846 break; 847 case 'u': case 'U': 848 cout << "Type device filename, or press <Enter> to exit: "; 849 device = ReadString(); 850 if (device.length() > 0) { 851 secondDevice = *this; 852 secondDevice.SetDisk(device); 853 secondDevice.SaveGPTData(0); 854 } // if 855 break; 856 case 'v': case 'V': 857 Verify(); 858 break; 859 case 'w': case 'W': 860 if (SaveGPTData() == 1) { 861 goOn = 0; 862 } // if 863 break; 864 case 'z': case 'Z': 865 if (DestroyGPTwPrompt() == 1) { 866 goOn = 0; 867 } 868 break; 869 default: 870 ShowExpertCommands(); 871 break; 872 } // switch 873 } while (goOn); 874 } // GPTDataTextUI::ExpertsMenu() 875 876 void GPTDataTextUI::ShowExpertCommands(void) { 877 cout << "a\tset attributes\n"; 878 cout << "c\tchange partition GUID\n"; 879 cout << "d\tdisplay the sector alignment value\n"; 880 cout << "e\trelocate backup data structures to the end of the disk\n"; 881 cout << "g\tchange disk GUID\n"; 882 cout << "h\trecompute CHS values in protective/hybrid MBR\n"; 883 cout << "i\tshow detailed information on a partition\n"; 884 cout << "l\tset the sector alignment value\n"; 885 cout << "m\treturn to main menu\n"; 886 cout << "n\tcreate a new protective MBR\n"; 887 cout << "o\tprint protective MBR data\n"; 888 cout << "p\tprint the partition table\n"; 889 cout << "q\tquit without saving changes\n"; 890 cout << "r\trecovery and transformation options (experts only)\n"; 891 cout << "s\tresize partition table\n"; 892 cout << "t\ttranspose two partition table entries\n"; 893 cout << "u\treplicate partition table on new device\n"; 894 cout << "v\tverify disk\n"; 895 cout << "w\twrite table to disk and exit\n"; 896 cout << "z\tzap (destroy) GPT data structures and exit\n"; 897 cout << "?\tprint this menu\n"; 898 } // GPTDataTextUI::ShowExpertCommands() 899 900 901 902 /******************************** 903 * * 904 * Non-class support functions. * 905 * * 906 ********************************/ 907 908 // GetMBRTypeCode() doesn't really belong in the class, since it's MBR- 909 // specific, but it's also user I/O-related, so I want to keep it in 910 // this file.... 911 912 // Get an MBR type code from the user and return it 913 int GetMBRTypeCode(int defType) { 914 string line; 915 int typeCode; 916 917 cout.setf(ios::uppercase); 918 cout.fill('0'); 919 do { 920 cout << "Enter an MBR hex code (default " << hex; 921 cout.width(2); 922 cout << defType << "): " << dec; 923 line = ReadString(); 924 if (line[0] == '\0') 925 typeCode = defType; 926 else 927 typeCode = StrToHex(line, 0); 928 } while ((typeCode <= 0) || (typeCode > 255)); 929 cout.fill(' '); 930 return typeCode; 931 } // GetMBRTypeCode 932 933 #ifdef USE_UTF16 934 // Note: ReadUString() is here rather than in support.cc so that the ICU 935 // libraries need not be linked to fixparts. 936 937 // Reads a Unicode string from stdin, returning it as an ICU-style string. 938 // Note that the returned string will NOT include the carriage return 939 // entered by the user. Relies on the ICU constructor from a string 940 // encoded in the current codepage to work. 941 UnicodeString ReadUString(void) { 942 return ReadString().c_str(); 943 } // ReadUString() 944 #endif 945 946